From 244554b564305bdbda4b0f0f4f209e5cefde7a10 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 23 May 2022 12:39:57 +0200 Subject: [PATCH] bump golang.org/x/tools to HEAD (#2875) * bump golang.org/x/tools to HEAD * fix: adapt linters to the new validation system. --- go.mod | 5 +- go.sum | 9 +- pkg/golinters/asciicheck.go | 4 +- pkg/golinters/bodyclose.go | 6 +- pkg/golinters/contextcheck.go | 3 +- pkg/golinters/deadcode.go | 17 ++- pkg/golinters/decorder.go | 4 +- pkg/golinters/depguard.go | 14 +- pkg/golinters/dogsled.go | 64 +++++---- pkg/golinters/dupl.go | 108 ++++++++------ pkg/golinters/durationcheck.go | 8 +- pkg/golinters/errcheck.go | 95 ++++++------ pkg/golinters/errname.go | 6 +- pkg/golinters/exhaustive.go | 8 +- pkg/golinters/exhaustruct.go | 9 +- pkg/golinters/forbidigo.go | 90 +++++++----- pkg/golinters/funlen.go | 74 ++++++---- pkg/golinters/gci.go | 1 - pkg/golinters/goanalysis/linter.go | 4 + pkg/golinters/gochecknoinits.go | 1 + pkg/golinters/gocognit.go | 78 +++++----- pkg/golinters/goconst.go | 55 +++---- pkg/golinters/gocritic.go | 194 ++++++++++++++----------- pkg/golinters/gocyclo.go | 71 +++++---- pkg/golinters/godot.go | 111 ++++++++------ pkg/golinters/godox.go | 69 +++++---- pkg/golinters/goerr113.go | 4 +- pkg/golinters/gofmt.go | 64 +++++---- pkg/golinters/gofmt_common.go | 10 +- pkg/golinters/gofumpt.go | 125 +++++++++------- pkg/golinters/goheader.go | 123 +++++++++------- pkg/golinters/goimports.go | 67 +++++---- pkg/golinters/golint.go | 94 ++++++------ pkg/golinters/gomoddirectives.go | 1 + pkg/golinters/gomodguard.go | 56 ++++---- pkg/golinters/gosec.go | 132 +++++++++-------- pkg/golinters/govet.go | 23 +-- pkg/golinters/interfacer.go | 73 ++++++---- pkg/golinters/lll.go | 113 ++++++++------- pkg/golinters/maintidx.go | 4 +- pkg/golinters/makezero.go | 73 ++++++---- pkg/golinters/maligned.go | 74 ++++++---- pkg/golinters/misspell.go | 178 ++++++++++++----------- pkg/golinters/nakedret.go | 99 +++++++------ pkg/golinters/nestif.go | 78 +++++----- pkg/golinters/nilerr.go | 1 + pkg/golinters/noctx.go | 6 +- pkg/golinters/nolintlint.go | 131 +++++++++-------- pkg/golinters/paralleltest.go | 6 +- pkg/golinters/prealloc.go | 56 ++++---- pkg/golinters/promlinter.go | 78 +++++----- pkg/golinters/revive.go | 124 +++++++++------- pkg/golinters/rowserrcheck.go | 18 +-- pkg/golinters/scopelint.go | 61 ++++---- pkg/golinters/sqlclosecheck.go | 8 +- pkg/golinters/structcheck.go | 76 ++++++---- pkg/golinters/tagliatelle.go | 8 +- pkg/golinters/tenv.go | 6 +- pkg/golinters/testpackage.go | 2 + pkg/golinters/tparallel.go | 6 +- pkg/golinters/typecheck.go | 8 +- pkg/golinters/unconvert.go | 68 +++++---- pkg/golinters/unparam.go | 94 ++++++------ pkg/golinters/unused.go | 86 ++++++----- pkg/golinters/varcheck.go | 59 +++++--- pkg/golinters/wastedassign.go | 6 +- pkg/golinters/whitespace.go | 109 ++++++++------ pkg/golinters/wsl.go | 125 ++++++++-------- pkg/lint/linter/config.go | 2 +- pkg/lint/lintersdb/manager.go | 208 ++++++++++++++++++--------- pkg/lint/load.go | 4 +- pkg/result/processors/nolint.go | 8 +- pkg/result/processors/nolint_test.go | 4 +- test/testdata/prealloc.go | 4 +- 74 files changed, 2222 insertions(+), 1649 deletions(-) diff --git a/go.mod b/go.mod index b12066768d83..a7ce24b087ae 100644 --- a/go.mod +++ b/go.mod @@ -99,7 +99,7 @@ require ( github.com/yagipy/maintidx v1.0.0 github.com/yeya24/promlinter v0.2.0 gitlab.com/bosi/decorder v0.2.1 - golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a + golang.org/x/tools v0.1.11-0.20220518213611-904e24e9fcf9 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b honnef.co/go/tools v0.3.1 mvdan.cc/gofumpt v0.3.1 @@ -165,11 +165,10 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 19ef03d2987c..cfb9e0d9e8b7 100644 --- a/go.sum +++ b/go.sum @@ -917,8 +917,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1207,10 +1208,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1220,8 +1219,8 @@ golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11-0.20220518213611-904e24e9fcf9 h1:/IsvdCr9GrirHTBBfgW/iJ4uDhMPf+OnOLrp4og7MzU= +golang.org/x/tools v0.1.11-0.20220518213611-904e24e9fcf9/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/golinters/asciicheck.go b/pkg/golinters/asciicheck.go index 1bf8c7b7da19..df301b417bc5 100644 --- a/pkg/golinters/asciicheck.go +++ b/pkg/golinters/asciicheck.go @@ -11,9 +11,7 @@ func NewAsciicheck() *goanalysis.Linter { return goanalysis.NewLinter( "asciicheck", "Simple linter to check that your code does not contain non-ASCII identifiers", - []*analysis.Analyzer{ - asciicheck.NewAnalyzer(), - }, + []*analysis.Analyzer{asciicheck.NewAnalyzer()}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/bodyclose.go b/pkg/golinters/bodyclose.go index 0e03813d1a77..e56bd83f28cb 100644 --- a/pkg/golinters/bodyclose.go +++ b/pkg/golinters/bodyclose.go @@ -8,14 +8,10 @@ import ( ) func NewBodyclose() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - bodyclose.Analyzer, - } - return goanalysis.NewLinter( "bodyclose", "checks whether HTTP response body is closed successfully", - analyzers, + []*analysis.Analyzer{bodyclose.Analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/contextcheck.go b/pkg/golinters/contextcheck.go index eb12ed4ef39f..38ede810dec9 100644 --- a/pkg/golinters/contextcheck.go +++ b/pkg/golinters/contextcheck.go @@ -8,11 +8,10 @@ import ( ) func NewContextCheck() *goanalysis.Linter { - analyzer := contextcheck.NewAnalyzer() return goanalysis.NewLinter( "contextcheck", "check the function whether use a non-inherited context", - []*analysis.Analyzer{analyzer}, + []*analysis.Analyzer{contextcheck.NewAnalyzer()}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/deadcode.go b/pkg/golinters/deadcode.go index 6ff38909fba4..408b180b935c 100644 --- a/pkg/golinters/deadcode.go +++ b/pkg/golinters/deadcode.go @@ -12,28 +12,36 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +const deadcodeName = "deadcode" + func NewDeadcode() *goanalysis.Linter { - const linterName = "deadcode" var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: linterName, + Name: deadcodeName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (interface{}, error) { prog := goanalysis.MakeFakeLoaderProgram(pass) + issues, err := deadcodeAPI.Run(prog) if err != nil { return nil, err } + res := make([]goanalysis.Issue, 0, len(issues)) for _, i := range issues { res = append(res, goanalysis.NewIssue(&result.Issue{ Pos: i.Pos, Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)), - FromLinter: linterName, + FromLinter: deadcodeName, }, pass)) } + + if len(issues) == 0 { + return nil, nil + } + mu.Lock() resIssues = append(resIssues, res...) mu.Unlock() @@ -41,8 +49,9 @@ func NewDeadcode() *goanalysis.Linter { return nil, nil }, } + return goanalysis.NewLinter( - linterName, + deadcodeName, "Finds unused code", []*analysis.Analyzer{analyzer}, nil, diff --git a/pkg/golinters/decorder.go b/pkg/golinters/decorder.go index 672f206ea35c..1c93acaa2c61 100644 --- a/pkg/golinters/decorder.go +++ b/pkg/golinters/decorder.go @@ -13,8 +13,6 @@ import ( func NewDecorder(settings *config.DecorderSettings) *goanalysis.Linter { a := decorder.Analyzer - analyzers := []*analysis.Analyzer{a} - // disable all rules/checks by default cfg := map[string]interface{}{ "disable-dec-num-check": true, @@ -32,7 +30,7 @@ func NewDecorder(settings *config.DecorderSettings) *goanalysis.Linter { return goanalysis.NewLinter( a.Name, a.Doc, - analyzers, + []*analysis.Analyzer{a}, map[string]map[string]interface{}{a.Name: cfg}, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/depguard.go b/pkg/golinters/depguard.go index 523db79b5a5e..6672330d0b62 100644 --- a/pkg/golinters/depguard.go +++ b/pkg/golinters/depguard.go @@ -15,23 +15,25 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -const depguardLinterName = "depguard" +const depguardName = "depguard" -func NewDepguard() *goanalysis.Linter { +func NewDepguard(settings *config.DepGuardSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: depguardLinterName, + Name: depguardName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( - depguardLinterName, + depguardName, "Go linter that checks if package imports are in a list of acceptable packages", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - dg, err := newDepGuard(&lintCtx.Settings().Depguard) + dg, err := newDepGuard(settings) analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { if err != nil { @@ -153,7 +155,7 @@ func (g guardian) run(loadConfig *loader.Config, prog *loader.Program, pass *ana goanalysis.NewIssue(&result.Issue{ Pos: issue.Position, Text: g.createMsg(issue.PackageName), - FromLinter: depguardLinterName, + FromLinter: depguardName, }, pass), ) } diff --git a/pkg/golinters/dogsled.go b/pkg/golinters/dogsled.go index 8978ff913dc9..00c32c2dc929 100644 --- a/pkg/golinters/dogsled.go +++ b/pkg/golinters/dogsled.go @@ -8,51 +8,65 @@ import ( "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -const dogsledLinterName = "dogsled" +const dogsledName = "dogsled" -func NewDogsled() *goanalysis.Linter { +//nolint:dupl +func NewDogsled(settings *config.DogsledSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: dogsledLinterName, + Name: dogsledName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - dogsledLinterName, - "Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var pkgIssues []goanalysis.Issue - for _, f := range pass.Files { - v := returnsVisitor{ - maxBlanks: lintCtx.Settings().Dogsled.MaxBlankIdentifiers, - f: pass.Fset, - } - ast.Walk(&v, f) - for i := range v.issues { - pkgIssues = append(pkgIssues, goanalysis.NewIssue(&v.issues[i], pass)) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runDogsled(pass, settings) + + if len(issues) == 0 { + return nil, nil } mu.Lock() - resIssues = append(resIssues, pkgIssues...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + dogsledName, + "Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } +func runDogsled(pass *analysis.Pass, settings *config.DogsledSettings) []goanalysis.Issue { + var reports []goanalysis.Issue + for _, f := range pass.Files { + v := &returnsVisitor{ + maxBlanks: settings.MaxBlankIdentifiers, + f: pass.Fset, + } + + ast.Walk(v, f) + + for i := range v.issues { + reports = append(reports, goanalysis.NewIssue(&v.issues[i], pass)) + } + } + + return reports +} + type returnsVisitor struct { f *token.FileSet maxBlanks int @@ -87,7 +101,7 @@ func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor { if numBlank > v.maxBlanks { v.issues = append(v.issues, result.Issue{ - FromLinter: dogsledLinterName, + FromLinter: dogsledName, Text: fmt.Sprintf("declaration has %v blank identifiers", numBlank), Pos: v.f.Position(assgnStmt.Pos()), }) diff --git a/pkg/golinters/dupl.go b/pkg/golinters/dupl.go index ed1c4fcbdc50..8c8d8fe4f310 100644 --- a/pkg/golinters/dupl.go +++ b/pkg/golinters/dupl.go @@ -9,36 +9,25 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -const duplLinterName = "dupl" +const duplName = "dupl" -func NewDupl() *goanalysis.Linter { +//nolint:dupl +func NewDupl(settings *config.DuplSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: duplLinterName, + Name: duplName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - duplLinterName, - "Tool for code clone detection", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) - } - - issues, err := duplAPI.Run(fileNames, lintCtx.Settings().Dupl.Threshold) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runDupl(pass, settings) if err != nil { return nil, err } @@ -47,37 +36,66 @@ func NewDupl() *goanalysis.Linter { return nil, nil } - res := make([]goanalysis.Issue, 0, len(issues)) - for _, i := range issues { - toFilename, err := fsutils.ShortestRelPath(i.To.Filename(), "") - if err != nil { - return nil, errors.Wrapf(err, "failed to get shortest rel path for %q", i.To.Filename()) - } - dupl := fmt.Sprintf("%s:%d-%d", toFilename, i.To.LineStart(), i.To.LineEnd()) - text := fmt.Sprintf("%d-%d lines are duplicate of %s", - i.From.LineStart(), i.From.LineEnd(), - formatCode(dupl, lintCtx.Cfg)) - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: token.Position{ - Filename: i.From.Filename(), - Line: i.From.LineStart(), - }, - LineRange: &result.Range{ - From: i.From.LineStart(), - To: i.From.LineEnd(), - }, - Text: text, - FromLinter: duplLinterName, - }, pass)) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + duplName, + "Tool for code clone detection", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runDupl(pass *analysis.Pass, settings *config.DuplSettings) ([]goanalysis.Issue, error) { + var fileNames []string + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + issues, err := duplAPI.Run(fileNames, settings.Threshold) + if err != nil { + return nil, err + } + + if len(issues) == 0 { + return nil, nil + } + + res := make([]goanalysis.Issue, 0, len(issues)) + + for _, i := range issues { + toFilename, err := fsutils.ShortestRelPath(i.To.Filename(), "") + if err != nil { + return nil, errors.Wrapf(err, "failed to get shortest rel path for %q", i.To.Filename()) + } + + dupl := fmt.Sprintf("%s:%d-%d", toFilename, i.To.LineStart(), i.To.LineEnd()) + text := fmt.Sprintf("%d-%d lines are duplicate of %s", + i.From.LineStart(), i.From.LineEnd(), + formatCode(dupl, nil)) + + res = append(res, goanalysis.NewIssue(&result.Issue{ + Pos: token.Position{ + Filename: i.From.Filename(), + Line: i.From.LineStart(), + }, + LineRange: &result.Range{ + From: i.From.LineStart(), + To: i.From.LineEnd(), + }, + Text: text, + FromLinter: duplName, + }, pass)) + } + + return res, nil +} diff --git a/pkg/golinters/durationcheck.go b/pkg/golinters/durationcheck.go index 9c452af50ea5..880de5d4200a 100644 --- a/pkg/golinters/durationcheck.go +++ b/pkg/golinters/durationcheck.go @@ -10,6 +10,10 @@ import ( func NewDurationCheck() *goanalysis.Linter { a := durationcheck.Analyzer - return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). - WithLoadMode(goanalysis.LoadModeTypesInfo) + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/errcheck.go b/pkg/golinters/errcheck.go index faea6bcc246a..53fe22e68c02 100644 --- a/pkg/golinters/errcheck.go +++ b/pkg/golinters/errcheck.go @@ -22,26 +22,27 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -func NewErrcheck() *goanalysis.Linter { - const linterName = "errcheck" +const errcheckName = "errcheck" +func NewErrcheck(settings *config.ErrcheckSettings) *goanalysis.Linter { var mu sync.Mutex - var res []goanalysis.Issue + var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: linterName, + Name: errcheckName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } return goanalysis.NewLinter( - linterName, + errcheckName, "Errcheck is a program for checking for unchecked errors "+ "in go programs. These unchecked errors can be critical bugs in some cases", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { // copied from errcheck - checker, err := getChecker(&lintCtx.Settings().Errcheck) + checker, err := getChecker(settings) if err != nil { lintCtx.Log.Errorf("failed to get checker: %v", err) return @@ -50,56 +51,66 @@ func NewErrcheck() *goanalysis.Linter { checker.Tags = lintCtx.Cfg.Run.BuildTags analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - pkg := &packages.Package{ - Fset: pass.Fset, - Syntax: pass.Files, - Types: pass.Pkg, - TypesInfo: pass.TypesInfo, + issues := runErrCheck(lintCtx, pass, checker) + if err != nil { + return nil, err } - errcheckIssues := checker.CheckPackage(pkg).Unique() - if len(errcheckIssues.UncheckedErrors) == 0 { + if len(issues) == 0 { return nil, nil } - issues := make([]goanalysis.Issue, len(errcheckIssues.UncheckedErrors)) - for i, err := range errcheckIssues.UncheckedErrors { - var text string - if err.FuncName != "" { - code := err.SelectorName - if err.SelectorName == "" { - code = err.FuncName - } - - text = fmt.Sprintf( - "Error return value of %s is not checked", - formatCode(code, lintCtx.Cfg), - ) - } else { - text = "Error return value is not checked" - } - - issues[i] = goanalysis.NewIssue( - &result.Issue{ - FromLinter: linterName, - Text: text, - Pos: err.Pos, - }, - pass, - ) - } - mu.Lock() - res = append(res, issues...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil } }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return res + return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } +func runErrCheck(lintCtx *linter.Context, pass *analysis.Pass, checker *errcheck.Checker) []goanalysis.Issue { + pkg := &packages.Package{ + Fset: pass.Fset, + Syntax: pass.Files, + Types: pass.Pkg, + TypesInfo: pass.TypesInfo, + } + + lintIssues := checker.CheckPackage(pkg).Unique() + if len(lintIssues.UncheckedErrors) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, len(lintIssues.UncheckedErrors)) + + for i, err := range lintIssues.UncheckedErrors { + text := "Error return value is not checked" + + if err.FuncName != "" { + code := err.SelectorName + if err.SelectorName == "" { + code = err.FuncName + } + + text = fmt.Sprintf("Error return value of %s is not checked", formatCode(code, lintCtx.Cfg)) + } + + issues[i] = goanalysis.NewIssue( + &result.Issue{ + FromLinter: errcheckName, + Text: text, + Pos: err.Pos, + }, + pass, + ) + } + + return issues +} + // parseIgnoreConfig was taken from errcheck in order to keep the API identical. // https://github.com/kisielk/errcheck/blob/1787c4bee836470bf45018cfbc783650db3c6501/main.go#L25-L60 func parseIgnoreConfig(s string) (map[string]*regexp.Regexp, error) { diff --git a/pkg/golinters/errname.go b/pkg/golinters/errname.go index 7ee8113478ba..96564cfa8cbe 100644 --- a/pkg/golinters/errname.go +++ b/pkg/golinters/errname.go @@ -8,14 +8,10 @@ import ( ) func NewErrName() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - analyzer.New(), - } - return goanalysis.NewLinter( "errname", "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.", - analyzers, + []*analysis.Analyzer{analyzer.New()}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/exhaustive.go b/pkg/golinters/exhaustive.go index ea264687df38..778dc004b29f 100644 --- a/pkg/golinters/exhaustive.go +++ b/pkg/golinters/exhaustive.go @@ -23,6 +23,10 @@ func NewExhaustive(settings *config.ExhaustiveSettings) *goanalysis.Linter { } } - return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, cfg). - WithLoadMode(goanalysis.LoadModeTypesInfo) + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/exhaustruct.go b/pkg/golinters/exhaustruct.go index 8c843289dd9e..37ea055d3ef7 100644 --- a/pkg/golinters/exhaustruct.go +++ b/pkg/golinters/exhaustruct.go @@ -10,7 +10,6 @@ import ( func NewExhaustruct(settings *config.ExhaustructSettings) *goanalysis.Linter { var include, exclude []string - if settings != nil { include = settings.Include exclude = settings.Exclude @@ -21,6 +20,10 @@ func NewExhaustruct(settings *config.ExhaustructSettings) *goanalysis.Linter { linterLogger.Fatalf("exhaustruct configuration: %v", err) } - return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). - WithLoadMode(goanalysis.LoadModeTypesInfo) + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/forbidigo.go b/pkg/golinters/forbidigo.go index 2fa9d51833e7..a8c256b9ee5a 100644 --- a/pkg/golinters/forbidigo.go +++ b/pkg/golinters/forbidigo.go @@ -7,64 +7,76 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewForbidigo() *goanalysis.Linter { - const linterName = "forbidigo" +const forbidigoName = "forbidigo" + +//nolint:dupl +func NewForbidigo(settings *config.ForbidigoSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: linterName, + Name: forbidigoName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - linterName, - "Forbids identifiers", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - s := &lintCtx.Settings().Forbidigo - - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var res []goanalysis.Issue - options := []forbidigo.Option{ - forbidigo.OptionExcludeGodocExamples(s.ExcludeGodocExamples), - // disable "//permit" directives so only "//nolint" directives matters within golangci lint - forbidigo.OptionIgnorePermitDirectives(true), - } - forbid, err := forbidigo.NewLinter(s.Forbid, options...) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runForbidigo(pass, settings) if err != nil { - return nil, errors.Wrapf(err, "failed to create linter %q", linterName) - } - - for _, file := range pass.Files { - hints, err := forbid.Run(pass.Fset, file) - if err != nil { - return nil, errors.Wrapf(err, "forbidigo linter failed on file %q", file.Name.String()) - } - for _, hint := range hints { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: hint.Position(), - Text: hint.Details(), - FromLinter: linterName, - }, pass)) - } + return nil, err } - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + forbidigoName, + "Forbids identifiers", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) ([]goanalysis.Issue, error) { + options := []forbidigo.Option{ + forbidigo.OptionExcludeGodocExamples(settings.ExcludeGodocExamples), + // disable "//permit" directives so only "//nolint" directives matters within golangci-lint + forbidigo.OptionIgnorePermitDirectives(true), + } + + forbid, err := forbidigo.NewLinter(settings.Forbid, options...) + if err != nil { + return nil, errors.Wrapf(err, "failed to create linter %q", forbidigoName) + } + + var issues []goanalysis.Issue + for _, file := range pass.Files { + hints, err := forbid.Run(pass.Fset, file) + if err != nil { + return nil, errors.Wrapf(err, "forbidigo linter failed on file %q", file.Name.String()) + } + + for _, hint := range hints { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: hint.Position(), + Text: hint.Details(), + FromLinter: forbidigoName, + }, pass)) + } + } + + return issues, nil +} diff --git a/pkg/golinters/funlen.go b/pkg/golinters/funlen.go index 29cb6b7ef700..c562c2aa0455 100644 --- a/pkg/golinters/funlen.go +++ b/pkg/golinters/funlen.go @@ -8,57 +8,69 @@ import ( "github.com/ultraware/funlen" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -const funlenLinterName = "funlen" +const funlenName = "funlen" -func NewFunlen() *goanalysis.Linter { +//nolint:dupl +func NewFunlen(settings *config.FunlenSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: funlenLinterName, + Name: funlenName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - funlenLinterName, - "Tool for detection of long functions", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var issues []funlen.Message - for _, file := range pass.Files { - fileIssues := funlen.Run(file, pass.Fset, lintCtx.Settings().Funlen.Lines, lintCtx.Settings().Funlen.Statements) - issues = append(issues, fileIssues...) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runFunlen(pass, settings) if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, len(issues)) - for k, i := range issues { - res[k] = goanalysis.NewIssue(&result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - Text: strings.TrimRight(i.Message, "\n"), - FromLinter: funlenLinterName, - }, pass) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + funlenName, + "Tool for detection of long functions", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runFunlen(pass *analysis.Pass, settings *config.FunlenSettings) []goanalysis.Issue { + var lintIssues []funlen.Message + for _, file := range pass.Files { + fileIssues := funlen.Run(file, pass.Fset, settings.Lines, settings.Statements) + lintIssues = append(lintIssues, fileIssues...) + } + + if len(lintIssues) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, len(lintIssues)) + for k, i := range lintIssues { + issues[k] = goanalysis.NewIssue(&result.Issue{ + Pos: token.Position{ + Filename: i.Pos.Filename, + Line: i.Pos.Line, + }, + Text: strings.TrimRight(i.Message, "\n"), + FromLinter: funlenName, + }, pass) + } + + return issues +} diff --git a/pkg/golinters/gci.go b/pkg/golinters/gci.go index c0c606a7b89d..b7487b8f1e9f 100644 --- a/pkg/golinters/gci.go +++ b/pkg/golinters/gci.go @@ -16,7 +16,6 @@ const gciName = "gci" func NewGci(settings *config.GciSettings) *goanalysis.Linter { var linterCfg map[string]map[string]interface{} - if settings != nil { cfg := map[string]interface{}{ gci.NoInlineCommentsFlag: settings.NoInlineComments, diff --git a/pkg/golinters/goanalysis/linter.go b/pkg/golinters/goanalysis/linter.go index ef49e4284aa3..eb27130cc9f4 100644 --- a/pkg/golinters/goanalysis/linter.go +++ b/pkg/golinters/goanalysis/linter.go @@ -211,3 +211,7 @@ func valueToString(v interface{}) string { return fmt.Sprint(v) } + +func DummyRun(_ *analysis.Pass) (interface{}, error) { + return nil, nil +} diff --git a/pkg/golinters/gochecknoinits.go b/pkg/golinters/gochecknoinits.go index f9715bda8683..bb0b783c6cab 100644 --- a/pkg/golinters/gochecknoinits.go +++ b/pkg/golinters/gochecknoinits.go @@ -41,6 +41,7 @@ func NewGochecknoinits() *goanalysis.Linter { return nil, nil }, } + return goanalysis.NewLinter( gochecknoinitsName, "Checks that no init functions are present in Go code", diff --git a/pkg/golinters/gocognit.go b/pkg/golinters/gocognit.go index eb42dd149cb5..49146c52c8f5 100644 --- a/pkg/golinters/gocognit.go +++ b/pkg/golinters/gocognit.go @@ -8,6 +8,7 @@ import ( "github.com/uudashr/gocognit" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -15,54 +16,65 @@ import ( const gocognitName = "gocognit" -func NewGocognit() *goanalysis.Linter { +//nolint:dupl +func NewGocognit(settings *config.GocognitSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: goanalysis.TheOnlyAnalyzerName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runGocognit(pass, settings) + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, } + return goanalysis.NewLinter( gocognitName, "Computes and checks the cognitive complexity of functions", []*analysis.Analyzer{analyzer}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var stats []gocognit.Stat - for _, f := range pass.Files { - stats = gocognit.ComplexityStats(f, pass.Fset, stats) - } - if len(stats) == 0 { - return nil, nil - } + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} - sort.SliceStable(stats, func(i, j int) bool { - return stats[i].Complexity > stats[j].Complexity - }) +func runGocognit(pass *analysis.Pass, settings *config.GocognitSettings) []goanalysis.Issue { + var stats []gocognit.Stat + for _, f := range pass.Files { + stats = gocognit.ComplexityStats(f, pass.Fset, stats) + } + if len(stats) == 0 { + return nil + } - res := make([]goanalysis.Issue, 0, len(stats)) - for _, s := range stats { - if s.Complexity <= lintCtx.Settings().Gocognit.MinComplexity { - break // Break as the stats is already sorted from greatest to least - } + sort.SliceStable(stats, func(i, j int) bool { + return stats[i].Complexity > stats[j].Complexity + }) - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: s.Pos, - Text: fmt.Sprintf("cognitive complexity %d of func %s is high (> %d)", - s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocognit.MinComplexity), - FromLinter: gocognitName, - }, pass)) - } + issues := make([]goanalysis.Issue, 0, len(stats)) + for _, s := range stats { + if s.Complexity <= settings.MinComplexity { + break // Break as the stats is already sorted from greatest to least + } - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: s.Pos, + Text: fmt.Sprintf("cognitive complexity %d of func %s is high (> %d)", + s.Complexity, formatCode(s.FuncName, nil), settings.MinComplexity), + FromLinter: gocognitName, + }, pass)) + } - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + return issues } diff --git a/pkg/golinters/goconst.go b/pkg/golinters/goconst.go index bdec4e10b079..24d3198b9a0d 100644 --- a/pkg/golinters/goconst.go +++ b/pkg/golinters/goconst.go @@ -7,6 +7,7 @@ import ( goconstAPI "github.com/jgautheron/goconst" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,40 +15,43 @@ import ( const goconstName = "goconst" -func NewGoconst() *goanalysis.Linter { +//nolint:dupl +func NewGoconst(settings *config.GoConstSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: goconstName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - goconstName, - "Finds repeated strings that could be replaced by a constant", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - issues, err := checkConstants(pass, lintCtx) - if err != nil || len(issues) == 0 { + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGoconst(pass, settings) + if err != nil { return nil, err } + if len(issues) == 0 { + return nil, nil + } + mu.Lock() resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + goconstName, + "Finds repeated strings that could be replaced by a constant", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } -func checkConstants(pass *analysis.Pass, lintCtx *linter.Context) ([]goanalysis.Issue, error) { - settings := lintCtx.Settings().Goconst - +func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanalysis.Issue, error) { cfg := goconstAPI.Config{ IgnoreTests: settings.IgnoreTests, MatchWithConstants: settings.MatchWithConstants, @@ -63,27 +67,28 @@ func checkConstants(pass *analysis.Pass, lintCtx *linter.Context) ([]goanalysis. cfg.ExcludeTypes[goconstAPI.Call] = true } - goconstIssues, err := goconstAPI.Run(pass.Files, pass.Fset, &cfg) + lintIssues, err := goconstAPI.Run(pass.Files, pass.Fset, &cfg) if err != nil { return nil, err } - if len(goconstIssues) == 0 { + if len(lintIssues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, 0, len(goconstIssues)) - for _, i := range goconstIssues { - textBegin := fmt.Sprintf("string %s has %d occurrences", formatCode(i.Str, lintCtx.Cfg), i.OccurrencesCount) - var textEnd string + res := make([]goanalysis.Issue, 0, len(lintIssues)) + for _, i := range lintIssues { + text := fmt.Sprintf("string %s has %d occurrences", formatCode(i.Str, nil), i.OccurrencesCount) + if i.MatchingConst == "" { - textEnd = ", make it a constant" + text += ", make it a constant" } else { - textEnd = fmt.Sprintf(", but such constant %s already exists", formatCode(i.MatchingConst, lintCtx.Cfg)) + text += fmt.Sprintf(", but such constant %s already exists", formatCode(i.MatchingConst, nil)) } + res = append(res, goanalysis.NewIssue(&result.Issue{ Pos: i.Pos, - Text: textBegin + textEnd, + Text: text, FromLinter: goconstName, }, pass)) } diff --git a/pkg/golinters/gocritic.go b/pkg/golinters/gocritic.go index ea3a3cbcbe8b..a53d57662461 100644 --- a/pkg/golinters/gocritic.go +++ b/pkg/golinters/gocritic.go @@ -22,130 +22,87 @@ import ( const gocriticName = "gocritic" -func NewGocritic() *goanalysis.Linter { +func NewGocritic(settings *config.GocriticSettings, cfg *config.Config) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue sizes := types.SizesFor("gc", runtime.GOARCH) + wrapper := goCriticWrapper{ + settings: settings, + cfg: cfg, + sizes: sizes, + } + analyzer := &analysis.Analyzer{ Name: gocriticName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - gocriticName, - `Provides diagnostics that check for bugs, performance and style issues. -Extensible without recompilation through dynamic rules. -Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.`, - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - linterCtx := gocriticlinter.NewContext(pass.Fset, sizes) - enabledCheckers, err := buildEnabledCheckers(lintCtx, linterCtx) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := wrapper.run(pass) if err != nil { return nil, err } - linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg) - pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files) - res := make([]goanalysis.Issue, 0, len(pkgIssues)) - - for i := range pkgIssues { - res = append(res, goanalysis.NewIssue(&pkgIssues[i], pass)) - } - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + gocriticName, + `Provides diagnostics that check for bugs, performance and style issues. +Extensible without recompilation through dynamic rules. +Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.`, + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter.CheckerParams { - // lowercase info param keys here because golangci-lint's config parser lowercases all strings - ret := gocriticlinter.CheckerParams{} - for k, v := range info.Params { - ret[strings.ToLower(k)] = v - } - - return ret +type goCriticWrapper struct { + settings *config.GocriticSettings + cfg *config.Config + sizes types.Sizes } -func configureCheckerInfo( - lintCtx *linter.Context, - info *gocriticlinter.CheckerInfo, - allParams map[string]config.GocriticCheckSettings) error { - params := allParams[strings.ToLower(info.Name)] - if params == nil { // no config for this checker - return nil +func (w goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { + linterCtx := gocriticlinter.NewContext(pass.Fset, w.sizes) + + enabledCheckers, err := w.buildEnabledCheckers(linterCtx) + if err != nil { + return nil, err } - infoParams := normalizeCheckerInfoParams(info) - for k, p := range params { - v, ok := infoParams[k] - if ok { - v.Value = normalizeCheckerParamsValue(lintCtx, p) - continue - } + linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg) - // param `k` isn't supported - if len(info.Params) == 0 { - return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params", - info.Name, k) - } + pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files) - var supportedKeys []string - for sk := range info.Params { - supportedKeys = append(supportedKeys, sk) - } - sort.Strings(supportedKeys) - - return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s", - info.Name, k, supportedKeys) + issues := make([]goanalysis.Issue, 0, len(pkgIssues)) + for i := range pkgIssues { + issues = append(issues, goanalysis.NewIssue(&pkgIssues[i], pass)) } - return nil + return issues, nil } -// normalizeCheckerParamsValue normalizes value types. -// go-critic asserts that CheckerParam.Value has some specific types, -// but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type. -// then we have to convert value types into the expected value types. -// Maybe in the future, this kind of conversion will be done in go-critic itself. -func normalizeCheckerParamsValue(lintCtx *linter.Context, p interface{}) interface{} { - rv := reflect.ValueOf(p) - switch rv.Type().Kind() { - case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: - return int(rv.Int()) - case reflect.Bool: - return rv.Bool() - case reflect.String: - // Perform variable substitution. - return strings.ReplaceAll(rv.String(), "${configDir}", lintCtx.Cfg.GetConfigDir()) - default: - return p - } -} - -func buildEnabledCheckers(lintCtx *linter.Context, linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) { - s := lintCtx.Settings().Gocritic - allParams := s.GetLowercasedParams() +func (w goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) { + allParams := w.settings.GetLowercasedParams() var enabledCheckers []*gocriticlinter.Checker for _, info := range gocriticlinter.GetCheckersInfo() { - if !s.IsCheckEnabled(info.Name) { + if !w.settings.IsCheckEnabled(info.Name) { continue } - if err := configureCheckerInfo(lintCtx, info, allParams); err != nil { + if err := w.configureCheckerInfo(info, allParams); err != nil { return nil, err } @@ -172,14 +129,14 @@ func runGocriticOnPackage(linterCtx *gocriticlinter.Context, checkers []*gocriti return res } -func runGocriticOnFile(ctx *gocriticlinter.Context, f *ast.File, checkers []*gocriticlinter.Checker) []result.Issue { +func runGocriticOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checkers []*gocriticlinter.Checker) []result.Issue { var res []result.Issue for _, c := range checkers { // All checkers are expected to use *lint.Context // as read-only structure, so no copying is required. for _, warn := range c.Check(f) { - pos := ctx.FileSet.Position(warn.Node.Pos()) + pos := linterCtx.FileSet.Position(warn.Node.Pos()) issue := result.Issue{ Pos: pos, Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), @@ -202,3 +159,66 @@ func runGocriticOnFile(ctx *gocriticlinter.Context, f *ast.File, checkers []*goc return res } + +func (w goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error { + params := allParams[strings.ToLower(info.Name)] + if params == nil { // no config for this checker + return nil + } + + infoParams := normalizeCheckerInfoParams(info) + for k, p := range params { + v, ok := infoParams[k] + if ok { + v.Value = w.normalizeCheckerParamsValue(p) + continue + } + + // param `k` isn't supported + if len(info.Params) == 0 { + return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params", + info.Name, k) + } + + var supportedKeys []string + for sk := range info.Params { + supportedKeys = append(supportedKeys, sk) + } + sort.Strings(supportedKeys) + + return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s", + info.Name, k, supportedKeys) + } + + return nil +} + +func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter.CheckerParams { + // lowercase info param keys here because golangci-lint's config parser lowercases all strings + ret := gocriticlinter.CheckerParams{} + for k, v := range info.Params { + ret[strings.ToLower(k)] = v + } + + return ret +} + +// normalizeCheckerParamsValue normalizes value types. +// go-critic asserts that CheckerParam.Value has some specific types, +// but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type. +// then we have to convert value types into the expected value types. +// Maybe in the future, this kind of conversion will be done in go-critic itself. +func (w goCriticWrapper) normalizeCheckerParamsValue(p interface{}) interface{} { + rv := reflect.ValueOf(p) + switch rv.Type().Kind() { + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + return int(rv.Int()) + case reflect.Bool: + return rv.Bool() + case reflect.String: + // Perform variable substitution. + return strings.ReplaceAll(rv.String(), "${configDir}", w.cfg.GetConfigDir()) + default: + return p + } +} diff --git a/pkg/golinters/gocyclo.go b/pkg/golinters/gocyclo.go index 5c61fec72c0b..ea82195711b5 100644 --- a/pkg/golinters/gocyclo.go +++ b/pkg/golinters/gocyclo.go @@ -7,6 +7,7 @@ import ( "github.com/fzipp/gocyclo" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,48 +15,62 @@ import ( const gocycloName = "gocyclo" -func NewGocyclo() *goanalysis.Linter { +//nolint:dupl +func NewGocyclo(settings *config.GoCycloSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: gocycloName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runGoCyclo(pass, settings) + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, } + return goanalysis.NewLinter( gocycloName, "Computes and checks the cyclomatic complexity of functions", []*analysis.Analyzer{analyzer}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var stats gocyclo.Stats - for _, f := range pass.Files { - stats = gocyclo.AnalyzeASTFile(f, pass.Fset, stats) - } - if len(stats) == 0 { - return nil, nil - } + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} - stats = stats.SortAndFilter(-1, lintCtx.Settings().Gocyclo.MinComplexity) +func runGoCyclo(pass *analysis.Pass, settings *config.GoCycloSettings) []goanalysis.Issue { + var stats gocyclo.Stats + for _, f := range pass.Files { + stats = gocyclo.AnalyzeASTFile(f, pass.Fset, stats) + } + if len(stats) == 0 { + return nil + } - res := make([]goanalysis.Issue, 0, len(stats)) - for _, s := range stats { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: s.Pos, - Text: fmt.Sprintf("cyclomatic complexity %d of func %s is high (> %d)", - s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocyclo.MinComplexity), - FromLinter: gocycloName, - }, pass)) - } + stats = stats.SortAndFilter(-1, settings.MinComplexity) - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() + issues := make([]goanalysis.Issue, 0, len(stats)) - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + for _, s := range stats { + text := fmt.Sprintf("cyclomatic complexity %d of func %s is high (> %d)", + s.Complexity, formatCode(s.FuncName, nil), settings.MinComplexity) + + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: s.Pos, + Text: text, + FromLinter: gocycloName, + }, pass)) + } + + return issues } diff --git a/pkg/golinters/godot.go b/pkg/golinters/godot.go index 09eb9b5b2e9f..72322fa717a9 100644 --- a/pkg/golinters/godot.go +++ b/pkg/golinters/godot.go @@ -6,6 +6,7 @@ import ( "github.com/tetafro/godot" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -13,73 +14,89 @@ import ( const godotName = "godot" -func NewGodot() *goanalysis.Linter { +func NewGodot(settings *config.GodotSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ - Name: godotName, - Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - godotName, - "Check if comments end in a period", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - cfg := lintCtx.Cfg.LintersSettings.Godot - settings := godot.Settings{ - Scope: godot.Scope(cfg.Scope), - Exclude: cfg.Exclude, - Period: cfg.Period, - Capital: cfg.Capital, + var dotSettings godot.Settings + + if settings != nil { + dotSettings = godot.Settings{ + Scope: godot.Scope(settings.Scope), + Exclude: settings.Exclude, + Period: settings.Period, + Capital: settings.Capital, } // Convert deprecated setting // todo(butuzov): remove on v2 release - if cfg.CheckAll { // nolint:staticcheck // Keep for retro-compatibility. - settings.Scope = godot.AllScope + if settings.CheckAll { // nolint:staticcheck // Keep for retro-compatibility. + dotSettings.Scope = godot.AllScope } + } - if settings.Scope == "" { - settings.Scope = godot.DeclScope - } + if dotSettings.Scope == "" { + dotSettings.Scope = godot.DeclScope + } - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var issues []godot.Issue - for _, file := range pass.Files { - iss, err := godot.Run(file, pass.Fset, settings) - if err != nil { - return nil, err - } - issues = append(issues, iss...) + analyzer := &analysis.Analyzer{ + Name: godotName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGodot(pass, dotSettings) + if err != nil { + return nil, err } if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, len(issues)) - for k, i := range issues { - issue := result.Issue{ - Pos: i.Pos, - Text: i.Message, - FromLinter: godotName, - Replacement: &result.Replacement{ - NewLines: []string{i.Replacement}, - }, - } - - res[k] = goanalysis.NewIssue(&issue, pass) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + godotName, + "Check if comments end in a period", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runGodot(pass *analysis.Pass, settings godot.Settings) ([]goanalysis.Issue, error) { + var lintIssues []godot.Issue + for _, file := range pass.Files { + iss, err := godot.Run(file, pass.Fset, settings) + if err != nil { + return nil, err + } + lintIssues = append(lintIssues, iss...) + } + + if len(lintIssues) == 0 { + return nil, nil + } + + issues := make([]goanalysis.Issue, len(lintIssues)) + for k, i := range lintIssues { + issue := result.Issue{ + Pos: i.Pos, + Text: i.Message, + FromLinter: godotName, + Replacement: &result.Replacement{ + NewLines: []string{i.Replacement}, + }, + } + + issues[k] = goanalysis.NewIssue(&issue, pass) + } + + return issues, nil +} diff --git a/pkg/golinters/godox.go b/pkg/golinters/godox.go index 2a4dd9fafb6d..4dba9df00307 100644 --- a/pkg/golinters/godox.go +++ b/pkg/golinters/godox.go @@ -8,6 +8,7 @@ import ( "github.com/matoous/godox" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -15,49 +16,61 @@ import ( const godoxName = "godox" -func NewGodox() *goanalysis.Linter { +//nolint:dupl +func NewGodox(settings *config.GodoxSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: godoxName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - godoxName, - "Tool for detection of FIXME, TODO and other comment keywords", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var issues []godox.Message - for _, file := range pass.Files { - issues = append(issues, godox.Run(file, pass.Fset, lintCtx.Settings().Godox.Keywords...)...) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runGodox(pass, settings) if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, len(issues)) - for k, i := range issues { - res[k] = goanalysis.NewIssue(&result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - Text: strings.TrimRight(i.Message, "\n"), - FromLinter: godoxName, - }, pass) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + godoxName, + "Tool for detection of FIXME, TODO and other comment keywords", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runGodox(pass *analysis.Pass, settings *config.GodoxSettings) []goanalysis.Issue { + var messages []godox.Message + for _, file := range pass.Files { + messages = append(messages, godox.Run(file, pass.Fset, settings.Keywords...)...) + } + + if len(messages) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, len(messages)) + + for k, i := range messages { + issues[k] = goanalysis.NewIssue(&result.Issue{ + Pos: token.Position{ + Filename: i.Pos.Filename, + Line: i.Pos.Line, + }, + Text: strings.TrimRight(i.Message, "\n"), + FromLinter: godoxName, + }, pass) + } + + return issues +} diff --git a/pkg/golinters/goerr113.go b/pkg/golinters/goerr113.go index 0c10005a0891..c97b6d587894 100644 --- a/pkg/golinters/goerr113.go +++ b/pkg/golinters/goerr113.go @@ -11,9 +11,7 @@ func NewGoerr113() *goanalysis.Linter { return goanalysis.NewLinter( "goerr113", "Golang linter to check the errors handling expressions", - []*analysis.Analyzer{ - err113.NewAnalyzer(), - }, + []*analysis.Analyzer{err113.NewAnalyzer()}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/gofmt.go b/pkg/golinters/gofmt.go index aa340dcf3c4e..1d50bfc55be9 100644 --- a/pkg/golinters/gofmt.go +++ b/pkg/golinters/gofmt.go @@ -7,20 +7,23 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" ) const gofmtName = "gofmt" -func NewGofmt() *goanalysis.Linter { +func NewGofmt(settings *config.GoFmtSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: gofmtName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( gofmtName, "Gofmt checks whether code was gofmt-ed. By default "+ @@ -29,31 +32,9 @@ func NewGofmt() *goanalysis.Linter { nil, ).WithContextSetter(func(lintCtx *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) - } - - var issues []goanalysis.Issue - - for _, f := range fileNames { - diff, err := gofmtAPI.Run(f, lintCtx.Settings().Gofmt.Simplify) - if err != nil { // TODO: skip - return nil, err - } - if diff == nil { - continue - } - - is, err := extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, gofmtName) - if err != nil { - return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff)) - } - - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) - } + issues, err := runGofmt(lintCtx, pass, settings) + if err != nil { + return nil, err } if len(issues) == 0 { @@ -70,3 +51,34 @@ func NewGofmt() *goanalysis.Linter { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runGofmt(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoFmtSettings) ([]goanalysis.Issue, error) { + var fileNames []string + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + var issues []goanalysis.Issue + + for _, f := range fileNames { + diff, err := gofmtAPI.Run(f, settings.Simplify) + if err != nil { // TODO: skip + return nil, err + } + if diff == nil { + continue + } + + is, err := extractIssuesFromPatch(string(diff), lintCtx, gofmtName) + if err != nil { + return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff)) + } + + for i := range is { + issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + } + } + + return issues, nil +} diff --git a/pkg/golinters/gofmt_common.go b/pkg/golinters/gofmt_common.go index 618662bebb34..a04cd7f86b67 100644 --- a/pkg/golinters/gofmt_common.go +++ b/pkg/golinters/gofmt_common.go @@ -229,7 +229,7 @@ func getErrorTextForLinter(lintCtx *linter.Context, linterName string) string { return text } -func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, linterName string) ([]result.Issue, error) { +func extractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName string) ([]result.Issue, error) { diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch)) if err != nil { return nil, errors.Wrap(err, "can't parse patch") @@ -239,18 +239,20 @@ func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Cont return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs) } - issues := []result.Issue{} + var issues []result.Issue for _, d := range diffs { if len(d.Hunks) == 0 { - log.Warnf("Got no hunks in diff %+v", d) + lintCtx.Log.Warnf("Got no hunks in diff %+v", d) continue } for _, hunk := range d.Hunks { p := hunkChangesParser{ - log: log, + log: lintCtx.Log, } + changes := p.parse(hunk) + for _, change := range changes { change := change // fix scope i := result.Issue{ diff --git a/pkg/golinters/gofumpt.go b/pkg/golinters/gofumpt.go index f0523459f4cc..60d97b944b08 100644 --- a/pkg/golinters/gofumpt.go +++ b/pkg/golinters/gofumpt.go @@ -3,6 +3,7 @@ package golinters import ( "bytes" "fmt" + "io" "os" "sync" @@ -18,71 +19,42 @@ import ( const gofumptName = "gofumpt" -func NewGofumpt() *goanalysis.Linter { +type differ interface { + Diff(out io.Writer, a io.ReadSeeker, b io.ReadSeeker) error +} + +func NewGofumpt(settings *config.GofumptSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - differ := difflib.New() + + diff := difflib.New() + + var options format.Options + + if settings != nil { + options = format.Options{ + LangVersion: getLangVersion(settings), + ModulePath: settings.ModulePath, + ExtraRules: settings.ExtraRules, + } + } analyzer := &analysis.Analyzer{ Name: gofumptName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( gofumptName, "Gofumpt checks whether code was gofumpt-ed.", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - settings := lintCtx.Settings().Gofumpt - - options := format.Options{ - LangVersion: getLangVersion(settings), - ModulePath: settings.ModulePath, - ExtraRules: settings.ExtraRules, - } - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) - } - - var issues []goanalysis.Issue - - for _, f := range fileNames { - input, err := os.ReadFile(f) - if err != nil { - return nil, fmt.Errorf("unable to open file %s: %w", f, err) - } - - output, err := format.Source(input, options) - if err != nil { - return nil, fmt.Errorf("error while running gofumpt: %w", err) - } - - if !bytes.Equal(input, output) { - out := bytes.Buffer{} - _, err = out.WriteString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", f)) - if err != nil { - return nil, fmt.Errorf("error while running gofumpt: %w", err) - } - - err = differ.Diff(&out, bytes.NewReader(input), bytes.NewReader(output)) - if err != nil { - return nil, fmt.Errorf("error while running gofumpt: %w", err) - } - - diff := out.String() - is, err := extractIssuesFromPatch(diff, lintCtx.Log, lintCtx, gofumptName) - if err != nil { - return nil, errors.Wrapf(err, "can't extract issues from gofumpt diff output %q", diff) - } - - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) - } - } + issues, err := runGofumpt(lintCtx, pass, diff, options) + if err != nil { + return nil, err } if len(issues) == 0 { @@ -100,8 +72,55 @@ func NewGofumpt() *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeSyntax) } -func getLangVersion(settings config.GofumptSettings) string { - if settings.LangVersion == "" { +func runGofumpt(lintCtx *linter.Context, pass *analysis.Pass, diff differ, options format.Options) ([]goanalysis.Issue, error) { + var fileNames []string + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + var issues []goanalysis.Issue + + for _, f := range fileNames { + input, err := os.ReadFile(f) + if err != nil { + return nil, fmt.Errorf("unable to open file %s: %w", f, err) + } + + output, err := format.Source(input, options) + if err != nil { + return nil, fmt.Errorf("error while running gofumpt: %w", err) + } + + if !bytes.Equal(input, output) { + out := bytes.Buffer{} + _, err = out.WriteString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", f)) + if err != nil { + return nil, fmt.Errorf("error while running gofumpt: %w", err) + } + + err = diff.Diff(&out, bytes.NewReader(input), bytes.NewReader(output)) + if err != nil { + return nil, fmt.Errorf("error while running gofumpt: %w", err) + } + + diff := out.String() + is, err := extractIssuesFromPatch(diff, lintCtx, gofumptName) + if err != nil { + return nil, errors.Wrapf(err, "can't extract issues from gofumpt diff output %q", diff) + } + + for i := range is { + issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + } + } + } + + return issues, nil +} + +func getLangVersion(settings *config.GofumptSettings) string { + if settings == nil || settings.LangVersion == "" { // TODO: defaults to "1.15", in the future (v2) must be set by using build.Default.ReleaseTags like staticcheck. return "1.15" } diff --git a/pkg/golinters/goheader.go b/pkg/golinters/goheader.go index fe83f83c6007..d7d27326ecba 100644 --- a/pkg/golinters/goheader.go +++ b/pkg/golinters/goheader.go @@ -7,6 +7,7 @@ import ( goheader "github.com/denis-tingaikin/go-header" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,72 +15,90 @@ import ( const goHeaderName = "goheader" -func NewGoHeader() *goanalysis.Linter { +func NewGoHeader(settings *config.GoHeaderSettings) *goanalysis.Linter { var mu sync.Mutex - var issues []goanalysis.Issue + var resIssues []goanalysis.Issue + + conf := &goheader.Configuration{} + if settings != nil { + conf = &goheader.Configuration{ + Values: settings.Values, + Template: settings.Template, + TemplatePath: settings.TemplatePath, + } + } analyzer := &analysis.Analyzer{ Name: goHeaderName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - goHeaderName, - "Checks is file header matches to pattern", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - cfg := lintCtx.Cfg.LintersSettings.Goheader - c := &goheader.Configuration{ - Values: cfg.Values, - Template: cfg.Template, - TemplatePath: cfg.TemplatePath, - } - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - if c.TemplatePath == "" && c.Template == "" { - // User did not pass template, so then do not run go-header linter - return nil, nil - } - template, err := c.GetTemplate() - if err != nil { - return nil, err - } - values, err := c.GetValues() + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGoHeader(pass, conf) if err != nil { return nil, err } - a := goheader.New(goheader.WithTemplate(template), goheader.WithValues(values)) - var res []goanalysis.Issue - for _, file := range pass.Files { - path := pass.Fset.Position(file.Pos()).Filename - i := a.Analyze(&goheader.Target{ - File: file, - Path: path, - }) - if i == nil { - continue - } - issue := result.Issue{ - Pos: token.Position{ - Line: i.Location().Line + 1, - Column: i.Location().Position, - Filename: path, - }, - Text: i.Message(), - FromLinter: goHeaderName, - } - res = append(res, goanalysis.NewIssue(&issue, pass)) - } - if len(res) == 0 { + + if len(issues) == 0 { return nil, nil } mu.Lock() - issues = append(issues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return issues + }, + } + + return goanalysis.NewLinter( + goHeaderName, + "Checks is file header matches to pattern", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runGoHeader(pass *analysis.Pass, conf *goheader.Configuration) ([]goanalysis.Issue, error) { + if conf.TemplatePath == "" && conf.Template == "" { + // User did not pass template, so then do not run go-header linter + return nil, nil + } + + template, err := conf.GetTemplate() + if err != nil { + return nil, err + } + + values, err := conf.GetValues() + if err != nil { + return nil, err + } + + a := goheader.New(goheader.WithTemplate(template), goheader.WithValues(values)) + + var issues []goanalysis.Issue + for _, file := range pass.Files { + path := pass.Fset.Position(file.Pos()).Filename + + i := a.Analyze(&goheader.Target{File: file, Path: path}) + + if i == nil { + continue + } + + issue := result.Issue{ + Pos: token.Position{ + Line: i.Location().Line + 1, + Column: i.Location().Position, + Filename: path, + }, + Text: i.Message(), + FromLinter: goHeaderName, + } + + issues = append(issues, goanalysis.NewIssue(&issue, pass)) + } + + return issues, nil +} diff --git a/pkg/golinters/goimports.go b/pkg/golinters/goimports.go index b58af1967602..e59ee3dd5bad 100644 --- a/pkg/golinters/goimports.go +++ b/pkg/golinters/goimports.go @@ -8,53 +8,35 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/imports" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" ) const goimportsName = "goimports" -func NewGoimports() *goanalysis.Linter { +func NewGoimports(settings *config.GoImportsSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: goimportsName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( goimportsName, "In addition to fixing imports, goimports also formats your code in the same style as gofmt.", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - imports.LocalPrefix = lintCtx.Settings().Goimports.LocalPrefixes - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) - } + imports.LocalPrefix = settings.LocalPrefixes - var issues []goanalysis.Issue - - for _, f := range fileNames { - diff, err := goimportsAPI.Run(f) - if err != nil { // TODO: skip - return nil, err - } - if diff == nil { - continue - } - - is, err := extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, goimportsName) - if err != nil { - return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff)) - } - - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) - } + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGoiImports(lintCtx, pass) + if err != nil { + return nil, err } if len(issues) == 0 { @@ -71,3 +53,34 @@ func NewGoimports() *goanalysis.Linter { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runGoiImports(lintCtx *linter.Context, pass *analysis.Pass) ([]goanalysis.Issue, error) { + var fileNames []string + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + var issues []goanalysis.Issue + + for _, f := range fileNames { + diff, err := goimportsAPI.Run(f) + if err != nil { // TODO: skip + return nil, err + } + if diff == nil { + continue + } + + is, err := extractIssuesFromPatch(string(diff), lintCtx, goimportsName) + if err != nil { + return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff)) + } + + for i := range is { + issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + } + } + + return issues, nil +} diff --git a/pkg/golinters/golint.go b/pkg/golinters/golint.go index 3b1b1b66f1fc..95c579e34ba8 100644 --- a/pkg/golinters/golint.go +++ b/pkg/golinters/golint.go @@ -2,35 +2,71 @@ package golinters import ( "fmt" - "go/ast" - "go/token" - "go/types" "sync" lintAPI "github.com/golangci/lint-1" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func golintProcessPkg(minConfidence float64, files []*ast.File, fset *token.FileSet, - typesPkg *types.Package, typesInfo *types.Info) ([]result.Issue, error) { +const golintName = "golint" + +//nolint:dupl +func NewGolint(settings *config.GoLintSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: golintName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGoLint(pass, settings) + if err != nil { + return nil, err + } + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, + } + + return goanalysis.NewLinter( + golintName, + "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} + +func runGoLint(pass *analysis.Pass, settings *config.GoLintSettings) ([]goanalysis.Issue, error) { l := new(lintAPI.Linter) - ps, err := l.LintPkg(files, fset, typesPkg, typesInfo) + + ps, err := l.LintPkg(pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo) if err != nil { - return nil, fmt.Errorf("can't lint %d files: %s", len(files), err) + return nil, fmt.Errorf("can't lint %d files: %s", len(pass.Files), err) } if len(ps) == 0 { return nil, nil } - issues := make([]result.Issue, 0, len(ps)) // This is worst case + lintIssues := make([]*result.Issue, 0, len(ps)) // This is worst case for idx := range ps { - if ps[idx].Confidence >= minConfidence { - issues = append(issues, result.Issue{ + if ps[idx].Confidence >= settings.MinConfidence { + lintIssues = append(lintIssues, &result.Issue{ Pos: ps[idx].Position, Text: ps[idx].Text, FromLinter: golintName, @@ -39,40 +75,10 @@ func golintProcessPkg(minConfidence float64, files []*ast.File, fset *token.File } } - return issues, nil -} - -const golintName = "golint" - -func NewGolint() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: golintName, - Doc: goanalysis.TheOnlyanalyzerDoc, + issues := make([]goanalysis.Issue, 0, len(lintIssues)) + for _, issue := range lintIssues { + issues = append(issues, goanalysis.NewIssue(issue, pass)) } - return goanalysis.NewLinter( - golintName, - "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - res, err := golintProcessPkg(lintCtx.Settings().Golint.MinConfidence, pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo) - if err != nil || len(res) == 0 { - return nil, err - } - - mu.Lock() - for i := range res { - resIssues = append(resIssues, goanalysis.NewIssue(&res[i], pass)) - } - mu.Unlock() - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) + return issues, nil } diff --git a/pkg/golinters/gomoddirectives.go b/pkg/golinters/gomoddirectives.go index 40d3bf786e09..81831129a235 100644 --- a/pkg/golinters/gomoddirectives.go +++ b/pkg/golinters/gomoddirectives.go @@ -30,6 +30,7 @@ func NewGoModDirectives(settings *config.GoModDirectivesSettings) *goanalysis.Li analyzer := &analysis.Analyzer{ Name: goanalysis.TheOnlyAnalyzerName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } return goanalysis.NewLinter( diff --git a/pkg/golinters/gomodguard.go b/pkg/golinters/gomodguard.go index 30ca6cc3dea3..76dd67012b56 100644 --- a/pkg/golinters/gomodguard.go +++ b/pkg/golinters/gomodguard.go @@ -6,6 +6,7 @@ import ( "github.com/ryancurrah/gomodguard" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -19,31 +20,18 @@ const ( ) // NewGomodguard returns a new Gomodguard linter. -func NewGomodguard() *goanalysis.Linter { - var ( - issues []goanalysis.Issue - mu = sync.Mutex{} - analyzer = &analysis.Analyzer{ - Name: goanalysis.TheOnlyAnalyzerName, - Doc: goanalysis.TheOnlyanalyzerDoc, - } - ) - - return goanalysis.NewLinter( - gomodguardName, - gomodguardDesc, - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - linterCfg := lintCtx.Cfg.LintersSettings.Gomodguard - - processorCfg := &gomodguard.Configuration{} - processorCfg.Allowed.Modules = linterCfg.Allowed.Modules - processorCfg.Allowed.Domains = linterCfg.Allowed.Domains - processorCfg.Blocked.LocalReplaceDirectives = linterCfg.Blocked.LocalReplaceDirectives - - for n := range linterCfg.Blocked.Modules { - for k, v := range linterCfg.Blocked.Modules[n] { +func NewGomodguard(settings *config.GoModGuardSettings) *goanalysis.Linter { + var issues []goanalysis.Issue + var mu sync.Mutex + + processorCfg := &gomodguard.Configuration{} + if settings != nil { + processorCfg.Allowed.Modules = settings.Allowed.Modules + processorCfg.Allowed.Domains = settings.Allowed.Domains + processorCfg.Blocked.LocalReplaceDirectives = settings.Blocked.LocalReplaceDirectives + + for n := range settings.Blocked.Modules { + for k, v := range settings.Blocked.Modules[n] { m := map[string]gomodguard.BlockedModule{k: { Recommendations: v.Recommendations, Reason: v.Reason, @@ -53,8 +41,8 @@ func NewGomodguard() *goanalysis.Linter { } } - for n := range linterCfg.Blocked.Versions { - for k, v := range linterCfg.Blocked.Versions[n] { + for n := range settings.Blocked.Versions { + for k, v := range settings.Blocked.Versions[n] { m := map[string]gomodguard.BlockedVersion{k: { Version: v.Version, Reason: v.Reason, @@ -63,7 +51,20 @@ func NewGomodguard() *goanalysis.Linter { break } } + } + + analyzer := &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, + } + return goanalysis.NewLinter( + gomodguardName, + gomodguardDesc, + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { processor, err := gomodguard.NewProcessor(processorCfg) if err != nil { lintCtx.Log.Warnf("running gomodguard failed: %s: if you are not using go modules "+ @@ -73,7 +74,6 @@ func NewGomodguard() *goanalysis.Linter { analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { var files []string - for _, file := range pass.Files { files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) } diff --git a/pkg/golinters/gosec.go b/pkg/golinters/gosec.go index 600a6e0f64fc..e861cc87aab6 100644 --- a/pkg/golinters/gosec.go +++ b/pkg/golinters/gosec.go @@ -27,7 +27,7 @@ func NewGosec(settings *config.GoSecSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - gasConfig := gosec.NewConfig() + conf := gosec.NewConfig() var filters []rules.RuleFilter if settings != nil { @@ -36,18 +36,20 @@ func NewGosec(settings *config.GoSecSettings) *goanalysis.Linter { for k, v := range settings.Config { // Uses ToUpper because the parsing of the map's key change the key to lowercase. // The value is not impacted by that: the case is respected. - gasConfig.Set(strings.ToUpper(k), v) + conf.Set(strings.ToUpper(k), v) } } - ruleDefinitions := rules.Generate(false, filters...) - logger := log.New(io.Discard, "", 0) + ruleDefinitions := rules.Generate(false, filters...) + analyzer := &analysis.Analyzer{ Name: gosecName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( gosecName, "Inspects source code for security problems", @@ -55,64 +57,14 @@ func NewGosec(settings *config.GoSecSettings) *goanalysis.Linter { nil, ).WithContextSetter(func(lintCtx *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - gosecAnalyzer := gosec.NewAnalyzer(gasConfig, true, settings.ExcludeGenerated, false, settings.Concurrency, logger) + // The `gosecAnalyzer` is here because of concurrency issue. + gosecAnalyzer := gosec.NewAnalyzer(conf, true, settings.ExcludeGenerated, false, settings.Concurrency, logger) gosecAnalyzer.LoadRules(ruleDefinitions.RulesInfo()) - pkg := &packages.Package{ - Fset: pass.Fset, - Syntax: pass.Files, - Types: pass.Pkg, - TypesInfo: pass.TypesInfo, - } - gosecAnalyzer.Check(pkg) - issues, _, _ := gosecAnalyzer.Report() - if len(issues) == 0 { - return nil, nil - } - severity, err := convertToScore(settings.Severity) - if err != nil { - lintCtx.Log.Warnf("The provided severity %v", err) - } - - confidence, err := convertToScore(settings.Confidence) - if err != nil { - lintCtx.Log.Warnf("The provided confidence %v", err) - } - issues = filterIssues(issues, severity, confidence) - res := make([]goanalysis.Issue, 0, len(issues)) - for _, i := range issues { - text := fmt.Sprintf("%s: %s", i.RuleID, i.What) // TODO: use severity and confidence - var r *result.Range - line, err := strconv.Atoi(i.Line) - if err != nil { - r = &result.Range{} - if n, rerr := fmt.Sscanf(i.Line, "%d-%d", &r.From, &r.To); rerr != nil || n != 2 { - lintCtx.Log.Warnf("Can't convert gosec line number %q of %v to int: %s", i.Line, i, err) - continue - } - line = r.From - } - - column, err := strconv.Atoi(i.Col) - if err != nil { - lintCtx.Log.Warnf("Can't convert gosec column number %q of %v to int: %s", i.Col, i, err) - continue - } - - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: token.Position{ - Filename: i.File, - Line: line, - Column: column, - }, - Text: text, - LineRange: r, - FromLinter: gosecName, - }, pass)) - } + issues := runGoSec(lintCtx, pass, settings, gosecAnalyzer) mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil @@ -122,6 +74,70 @@ func NewGosec(settings *config.GoSecSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeTypesInfo) } +func runGoSec(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoSecSettings, analyzer *gosec.Analyzer) []goanalysis.Issue { + pkg := &packages.Package{ + Fset: pass.Fset, + Syntax: pass.Files, + Types: pass.Pkg, + TypesInfo: pass.TypesInfo, + } + + analyzer.Check(pkg) + + secIssues, _, _ := analyzer.Report() + if len(secIssues) == 0 { + return nil + } + + severity, err := convertToScore(settings.Severity) + if err != nil { + lintCtx.Log.Warnf("The provided severity %v", err) + } + + confidence, err := convertToScore(settings.Confidence) + if err != nil { + lintCtx.Log.Warnf("The provided confidence %v", err) + } + + secIssues = filterIssues(secIssues, severity, confidence) + + issues := make([]goanalysis.Issue, 0, len(secIssues)) + for _, i := range secIssues { + text := fmt.Sprintf("%s: %s", i.RuleID, i.What) // TODO: use severity and confidence + + var r *result.Range + + line, err := strconv.Atoi(i.Line) + if err != nil { + r = &result.Range{} + if n, rerr := fmt.Sscanf(i.Line, "%d-%d", &r.From, &r.To); rerr != nil || n != 2 { + lintCtx.Log.Warnf("Can't convert gosec line number %q of %v to int: %s", i.Line, i, err) + continue + } + line = r.From + } + + column, err := strconv.Atoi(i.Col) + if err != nil { + lintCtx.Log.Warnf("Can't convert gosec column number %q of %v to int: %s", i.Col, i, err) + continue + } + + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: token.Position{ + Filename: i.File, + Line: line, + Column: column, + }, + Text: text, + LineRange: r, + FromLinter: gosecName, + }, pass)) + } + + return issues +} + // based on https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/cmd/gosec/main.go#L170-L188 func gosecRuleFilters(includes, excludes []string) []rules.RuleFilter { var filters []rules.RuleFilter diff --git a/pkg/golinters/govet.go b/pkg/golinters/govet.go index c7a58af610f8..3f005c1d5cc2 100644 --- a/pkg/golinters/govet.go +++ b/pkg/golinters/govet.go @@ -119,33 +119,34 @@ var ( } ) -func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter { - var settings map[string]map[string]interface{} - if cfg != nil { - settings = cfg.Settings +func NewGovet(settings *config.GovetSettings) *goanalysis.Linter { + var conf map[string]map[string]interface{} + if settings != nil { + conf = settings.Settings } + return goanalysis.NewLinter( "govet", "Vet examines Go source code and reports suspicious constructs, "+ "such as Printf calls whose arguments do not align with the format string", - analyzersFromConfig(cfg), - settings, + analyzersFromConfig(settings), + conf, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func analyzersFromConfig(cfg *config.GovetSettings) []*analysis.Analyzer { - if cfg == nil { +func analyzersFromConfig(settings *config.GovetSettings) []*analysis.Analyzer { + if settings == nil { return defaultAnalyzers } - if cfg.CheckShadowing { + if settings.CheckShadowing { // Keeping for backward compatibility. - cfg.Enable = append(cfg.Enable, shadow.Analyzer.Name) + settings.Enable = append(settings.Enable, shadow.Analyzer.Name) } var enabledAnalyzers []*analysis.Analyzer for _, a := range allAnalyzers { - if isAnalyzerEnabled(a.Name, cfg, defaultAnalyzers) { + if isAnalyzerEnabled(a.Name, settings, defaultAnalyzers) { enabledAnalyzers = append(enabledAnalyzers, a) } } diff --git a/pkg/golinters/interfacer.go b/pkg/golinters/interfacer.go index 1edbe894cc6a..59125c5c7b4d 100644 --- a/pkg/golinters/interfacer.go +++ b/pkg/golinters/interfacer.go @@ -22,46 +22,61 @@ func NewInterfacer() *goanalysis.Linter { Name: interfacerName, Doc: goanalysis.TheOnlyanalyzerDoc, Requires: []*analysis.Analyzer{buildssa.Analyzer}, - } - return goanalysis.NewLinter( - interfacerName, - "Linter that suggests narrower interface types", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) - ssaPkg := ssa.Pkg - c := &check.Checker{} - prog := goanalysis.MakeFakeLoaderProgram(pass) - c.Program(prog) - c.ProgramSSA(ssaPkg.Prog) - - issues, err := c.Check() + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runInterfacer(pass) if err != nil { return nil, err } + if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, 0, len(issues)) - for _, i := range issues { - pos := pass.Fset.Position(i.Pos()) - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: pos, - Text: i.Message(), - FromLinter: interfacerName, - }, pass)) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + interfacerName, + "Linter that suggests narrower interface types", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +func runInterfacer(pass *analysis.Pass) ([]goanalysis.Issue, error) { + c := &check.Checker{} + + prog := goanalysis.MakeFakeLoaderProgram(pass) + c.Program(prog) + + ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) + ssaPkg := ssa.Pkg + c.ProgramSSA(ssaPkg.Prog) + + lintIssues, err := c.Check() + if err != nil { + return nil, err + } + if len(lintIssues) == 0 { + return nil, nil + } + + issues := make([]goanalysis.Issue, 0, len(lintIssues)) + for _, i := range lintIssues { + pos := pass.Fset.Position(i.Pos()) + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: pos, + Text: i.Message(), + FromLinter: interfacerName, + }, pass)) + } + + return issues, nil +} diff --git a/pkg/golinters/lll.go b/pkg/golinters/lll.go index e0a9de63c675..65d4c15a3784 100644 --- a/pkg/golinters/lll.go +++ b/pkg/golinters/lll.go @@ -11,11 +11,74 @@ import ( "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) +const lllName = "lll" + +//nolint:dupl +func NewLLL(settings *config.LllSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: lllName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runLll(pass, settings) + if err != nil { + return nil, err + } + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, + } + + return goanalysis.NewLinter( + lllName, + "Reports long lines", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} + +func runLll(pass *analysis.Pass, settings *config.LllSettings) ([]goanalysis.Issue, error) { + var fileNames []string + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + spaces := strings.Repeat(" ", settings.TabWidth) + + var issues []goanalysis.Issue + for _, f := range fileNames { + lintIssues, err := getLLLIssuesForFile(f, settings.LineLength, spaces) + if err != nil { + return nil, err + } + + for i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) + } + } + + return issues, nil +} + func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) { var res []result.Issue @@ -72,53 +135,3 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r return res, nil } - -const lllName = "lll" - -func NewLLL() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: lllName, - Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - lllName, - "Reports long lines", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) - } - - var res []goanalysis.Issue - spaces := strings.Repeat(" ", lintCtx.Settings().Lll.TabWidth) - for _, f := range fileNames { - issues, err := getLLLIssuesForFile(f, lintCtx.Settings().Lll.LineLength, spaces) - if err != nil { - return nil, err - } - for i := range issues { - res = append(res, goanalysis.NewIssue(&issues[i], pass)) - } - } - - if len(res) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() - - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} diff --git a/pkg/golinters/maintidx.go b/pkg/golinters/maintidx.go index 2b02b948f47e..183006b05c5e 100644 --- a/pkg/golinters/maintidx.go +++ b/pkg/golinters/maintidx.go @@ -24,9 +24,7 @@ func NewMaintIdx(cfg *config.MaintIdxSettings) *goanalysis.Linter { return goanalysis.NewLinter( analyzer.Name, analyzer.Doc, - []*analysis.Analyzer{ - analyzer, - }, + []*analysis.Analyzer{analyzer}, cfgMap, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/makezero.go b/pkg/golinters/makezero.go index cdde09291d22..5d55f01e7703 100644 --- a/pkg/golinters/makezero.go +++ b/pkg/golinters/makezero.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,47 +15,61 @@ import ( const makezeroName = "makezero" -func NewMakezero() *goanalysis.Linter { +//nolint:dupl +func NewMakezero(settings *config.MakezeroSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: makezeroName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - makezeroName, - "Finds slice declarations with non-zero initial length", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - s := &lintCtx.Settings().Makezero - - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var res []goanalysis.Issue - linter := makezero.NewLinter(s.Always) - for _, file := range pass.Files { - hints, err := linter.Run(pass.Fset, pass.TypesInfo, file) - if err != nil { - return nil, errors.Wrapf(err, "makezero linter failed on file %q", file.Name.String()) - } - for _, hint := range hints { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: hint.Position(), - Text: hint.Details(), - FromLinter: makezeroName, - }, pass)) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runMakeZero(pass, settings) + if err != nil { + return nil, err } - if len(res) == 0 { + + if len(issues) == 0 { return nil, nil } + mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + makezeroName, + "Finds slice declarations with non-zero initial length", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +func runMakeZero(pass *analysis.Pass, settings *config.MakezeroSettings) ([]goanalysis.Issue, error) { + zero := makezero.NewLinter(settings.Always) + + var issues []goanalysis.Issue + + for _, file := range pass.Files { + hints, err := zero.Run(pass.Fset, pass.TypesInfo, file) + if err != nil { + return nil, errors.Wrapf(err, "makezero linter failed on file %q", file.Name.String()) + } + + for _, hint := range hints { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: hint.Position(), + Text: hint.Details(), + FromLinter: makezeroName, + }, pass)) + } + } + + return issues, nil +} diff --git a/pkg/golinters/maligned.go b/pkg/golinters/maligned.go index 22422b8c6a4a..9c3ca8b5fe6e 100644 --- a/pkg/golinters/maligned.go +++ b/pkg/golinters/maligned.go @@ -7,52 +7,68 @@ import ( malignedAPI "github.com/golangci/maligned" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewMaligned() *goanalysis.Linter { - const linterName = "maligned" +const malignedName = "maligned" + +//nolint:dupl +func NewMaligned(settings *config.MalignedSettings) *goanalysis.Linter { var mu sync.Mutex var res []goanalysis.Issue + analyzer := &analysis.Analyzer{ - Name: linterName, + Name: malignedName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - linterName, - "Tool to detect Go structs that would take less memory if their fields were sorted", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - prog := goanalysis.MakeFakeLoaderProgram(pass) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runMaligned(pass, settings) - malignedIssues := malignedAPI.Run(prog) - if len(malignedIssues) == 0 { + if len(issues) == 0 { return nil, nil } - issues := make([]goanalysis.Issue, 0, len(malignedIssues)) - for _, i := range malignedIssues { - text := fmt.Sprintf("struct of size %d bytes could be of size %d bytes", i.OldSize, i.NewSize) - if lintCtx.Settings().Maligned.SuggestNewOrder { - text += fmt.Sprintf(":\n%s", formatCodeBlock(i.NewStructDef, lintCtx.Cfg)) - } - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: text, - FromLinter: linterName, - }, pass)) - } - mu.Lock() res = append(res, issues...) mu.Unlock() + return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + malignedName, + "Tool to detect Go structs that would take less memory if their fields were sorted", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return res }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +func runMaligned(pass *analysis.Pass, settings *config.MalignedSettings) []goanalysis.Issue { + prog := goanalysis.MakeFakeLoaderProgram(pass) + + malignedIssues := malignedAPI.Run(prog) + if len(malignedIssues) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, 0, len(malignedIssues)) + for _, i := range malignedIssues { + text := fmt.Sprintf("struct of size %d bytes could be of size %d bytes", i.OldSize, i.NewSize) + if settings.SuggestNewOrder { + text += fmt.Sprintf(":\n%s", formatCodeBlock(i.NewStructDef, nil)) + } + + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: i.Pos, + Text: text, + FromLinter: malignedName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/misspell.go b/pkg/golinters/misspell.go index 80ecf9bb668a..47eb7a7b924f 100644 --- a/pkg/golinters/misspell.go +++ b/pkg/golinters/misspell.go @@ -9,119 +9,48 @@ import ( "github.com/golangci/misspell" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func runMisspellOnFile(fileName string, r *misspell.Replacer, lintCtx *linter.Context) ([]result.Issue, error) { - var res []result.Issue - fileContent, err := lintCtx.FileCache.GetFileBytes(fileName) - if err != nil { - return nil, fmt.Errorf("can't get file %s contents: %s", fileName, err) - } - - // use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find - // issues inside strings: it searches only inside comments. r.Replace - // searches all words: it treats input as a plain text. A standalone misspell - // tool uses r.Replace by default. - _, diffs := r.Replace(string(fileContent)) - for _, diff := range diffs { - text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected) - pos := token.Position{ - Filename: fileName, - Line: diff.Line, - Column: diff.Column + 1, - } - replacement := &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: diff.Column, - Length: len(diff.Original), - NewString: diff.Corrected, - }, - } - - res = append(res, result.Issue{ - Pos: pos, - Text: text, - FromLinter: misspellName, - Replacement: replacement, - }) - } - - return res, nil -} - const misspellName = "misspell" -func NewMisspell() *goanalysis.Linter { +func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - var ruleErr error analyzer := &analysis.Analyzer{ Name: misspellName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( misspellName, "Finds commonly misspelled English words in comments", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - r := misspell.Replacer{ - Replacements: misspell.DictMain, - } - - // Figure out regional variations - settings := lintCtx.Settings().Misspell - locale := settings.Locale - switch strings.ToUpper(locale) { - case "": - // nothing - case "US": - r.AddRuleList(misspell.DictAmerican) - case "UK", "GB": - r.AddRuleList(misspell.DictBritish) - case "NZ", "AU", "CA": - ruleErr = fmt.Errorf("unknown locale: %q", locale) - } - - if ruleErr == nil { - if len(settings.IgnoreWords) != 0 { - r.RemoveRule(settings.IgnoreWords) - } - - r.Compile() - } + replacer, ruleErr := createMisspellReplacer(settings) analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { if ruleErr != nil { return nil, ruleErr } - var fileNames []string - for _, f := range pass.Files { - pos := pass.Fset.PositionFor(f.Pos(), false) - fileNames = append(fileNames, pos.Filename) + issues, err := runMisspell(lintCtx, pass, replacer) + if err != nil { + return nil, err } - var res []goanalysis.Issue - for _, f := range fileNames { - issues, err := runMisspellOnFile(f, &r, lintCtx) - if err != nil { - return nil, err - } - for i := range issues { - res = append(res, goanalysis.NewIssue(&issues[i], pass)) - } - } - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil @@ -130,3 +59,90 @@ func NewMisspell() *goanalysis.Linter { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer) ([]goanalysis.Issue, error) { + var fileNames []string + + for _, f := range pass.Files { + pos := pass.Fset.PositionFor(f.Pos(), false) + fileNames = append(fileNames, pos.Filename) + } + + var issues []goanalysis.Issue + for _, filename := range fileNames { + lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer) + if err != nil { + return nil, err + } + for i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) + } + } + + return issues, nil +} + +func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replacer, error) { + replacer := &misspell.Replacer{ + Replacements: misspell.DictMain, + } + + // Figure out regional variations + switch strings.ToUpper(settings.Locale) { + case "": + // nothing + case "US": + replacer.AddRuleList(misspell.DictAmerican) + case "UK", "GB": + replacer.AddRuleList(misspell.DictBritish) + case "NZ", "AU", "CA": + return nil, fmt.Errorf("unknown locale: %q", settings.Locale) + } + + if len(settings.IgnoreWords) != 0 { + replacer.RemoveRule(settings.IgnoreWords) + } + + // It can panic. + replacer.Compile() + + return replacer, nil +} + +func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer) ([]result.Issue, error) { + var res []result.Issue + fileContent, err := lintCtx.FileCache.GetFileBytes(filename) + if err != nil { + return nil, fmt.Errorf("can't get file %s contents: %s", filename, err) + } + + // use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find + // issues inside strings: it searches only inside comments. r.Replace + // searches all words: it treats input as a plain text. A standalone misspell + // tool uses r.Replace by default. + _, diffs := replacer.Replace(string(fileContent)) + for _, diff := range diffs { + text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected) + pos := token.Position{ + Filename: filename, + Line: diff.Line, + Column: diff.Column + 1, + } + replacement := &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: diff.Column, + Length: len(diff.Original), + NewString: diff.Corrected, + }, + } + + res = append(res, result.Issue{ + Pos: pos, + Text: text, + FromLinter: misspellName, + Replacement: replacement, + }) + } + + return res, nil +} diff --git a/pkg/golinters/nakedret.go b/pkg/golinters/nakedret.go index 86735a51ada5..dc2de0345a7a 100644 --- a/pkg/golinters/nakedret.go +++ b/pkg/golinters/nakedret.go @@ -8,11 +8,66 @@ import ( "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) +const nakedretName = "nakedret" + +//nolint:dupl +func NewNakedret(settings *config.NakedretSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: nakedretName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runNakedRet(pass, settings) + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, + } + + return goanalysis.NewLinter( + nakedretName, + "Finds naked returns in functions greater than a specified function length", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} + +func runNakedRet(pass *analysis.Pass, settings *config.NakedretSettings) []goanalysis.Issue { + var issues []goanalysis.Issue + + for _, file := range pass.Files { + v := nakedretVisitor{ + maxLength: settings.MaxFuncLines, + f: pass.Fset, + } + + ast.Walk(&v, file) + + for i := range v.issues { + issues = append(issues, goanalysis.NewIssue(&v.issues[i], pass)) + } + } + + return issues +} + type nakedretVisitor struct { maxLength int f *token.FileSet @@ -77,47 +132,3 @@ func (v *nakedretVisitor) Visit(node ast.Node) ast.Visitor { v.processFuncDecl(funcDecl) return v } - -const nakedretName = "nakedret" - -func NewNakedret() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: nakedretName, - Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - nakedretName, - "Finds naked returns in functions greater than a specified function length", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var res []goanalysis.Issue - for _, file := range pass.Files { - v := nakedretVisitor{ - maxLength: lintCtx.Settings().Nakedret.MaxFuncLines, - f: pass.Fset, - } - ast.Walk(&v, file) - for i := range v.issues { - res = append(res, goanalysis.NewIssue(&v.issues[i], pass)) - } - } - - if len(res) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() - - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} diff --git a/pkg/golinters/nestif.go b/pkg/golinters/nestif.go index 0998a8ce2f6c..78a516f9d1ec 100644 --- a/pkg/golinters/nestif.go +++ b/pkg/golinters/nestif.go @@ -7,6 +7,7 @@ import ( "github.com/nakabonne/nestif" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,52 +15,65 @@ import ( const nestifName = "nestif" -func NewNestif() *goanalysis.Linter { +//nolint:dupl +func NewNestif(settings *config.NestifSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: goanalysis.TheOnlyAnalyzerName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - nestifName, - "Reports deeply nested if statements", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - checker := &nestif.Checker{ - MinComplexity: lintCtx.Settings().Nestif.MinComplexity, - } - var issues []nestif.Issue - for _, f := range pass.Files { - issues = append(issues, checker.Check(f, pass.Fset)...) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runNestIf(pass, settings) + if len(issues) == 0 { return nil, nil } - sort.SliceStable(issues, func(i, j int) bool { - return issues[i].Complexity > issues[j].Complexity - }) - - res := make([]goanalysis.Issue, 0, len(issues)) - for _, i := range issues { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: i.Message, - FromLinter: nestifName, - }, pass)) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + nestifName, + "Reports deeply nested if statements", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runNestIf(pass *analysis.Pass, settings *config.NestifSettings) []goanalysis.Issue { + checker := &nestif.Checker{ + MinComplexity: settings.MinComplexity, + } + + var lintIssues []nestif.Issue + for _, f := range pass.Files { + lintIssues = append(lintIssues, checker.Check(f, pass.Fset)...) + } + + if len(lintIssues) == 0 { + return nil + } + + sort.SliceStable(lintIssues, func(i, j int) bool { + return lintIssues[i].Complexity > lintIssues[j].Complexity + }) + + issues := make([]goanalysis.Issue, 0, len(lintIssues)) + for _, i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: i.Pos, + Text: i.Message, + FromLinter: nestifName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/nilerr.go b/pkg/golinters/nilerr.go index d8a9a613eff4..2ea16f2f3958 100644 --- a/pkg/golinters/nilerr.go +++ b/pkg/golinters/nilerr.go @@ -9,6 +9,7 @@ import ( func NewNilErr() *goanalysis.Linter { a := nilerr.Analyzer + return goanalysis.NewLinter( a.Name, "Finds the code that returns nil even if it checks that the error is not nil.", diff --git a/pkg/golinters/noctx.go b/pkg/golinters/noctx.go index b5c4a4be240b..b7fcd2a737af 100644 --- a/pkg/golinters/noctx.go +++ b/pkg/golinters/noctx.go @@ -8,14 +8,10 @@ import ( ) func NewNoctx() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - noctx.Analyzer, - } - return goanalysis.NewLinter( "noctx", "noctx finds sending http request without context.Context", - analyzers, + []*analysis.Analyzer{noctx.Analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/nolintlint.go b/pkg/golinters/nolintlint.go index 889cff864c89..62b8064ba2ae 100644 --- a/pkg/golinters/nolintlint.go +++ b/pkg/golinters/nolintlint.go @@ -7,87 +7,102 @@ import ( "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/golinters/nolintlint" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -const NolintlintName = "nolintlint" +const NoLintLintName = "nolintlint" -func NewNoLintLint() *goanalysis.Linter { +//nolint:dupl +func NewNoLintLint(settings *config.NoLintLintSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: NolintlintName, + Name: NoLintLintName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - NolintlintName, - "Reports ill-formed or insufficient nolint directives", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var needs nolintlint.Needs - settings := lintCtx.Settings().NoLintLint - if settings.RequireExplanation { - needs |= nolintlint.NeedsExplanation - } - if !settings.AllowLeadingSpace { - needs |= nolintlint.NeedsMachineOnly - } - if settings.RequireSpecific { - needs |= nolintlint.NeedsSpecific - } - if !settings.AllowUnused { - needs |= nolintlint.NeedsUnused - } - - lnt, err := nolintlint.NewLinter(needs, settings.AllowNoExplanation) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runNoLintLint(pass, settings) if err != nil { return nil, err } - nodes := make([]ast.Node, 0, len(pass.Files)) - for _, n := range pass.Files { - nodes = append(nodes, n) - } - issues, err := lnt.Run(pass.Fset, nodes...) - if err != nil { - return nil, fmt.Errorf("linter failed to run: %s", err) - } - var res []goanalysis.Issue - for _, i := range issues { - expectNoLint := false - var expectedNolintLinter string - if ii, ok := i.(nolintlint.UnusedCandidate); ok { - expectedNolintLinter = ii.ExpectedLinter - expectNoLint = true - } - issue := &result.Issue{ - FromLinter: NolintlintName, - Text: i.Details(), - Pos: i.Position(), - ExpectNoLint: expectNoLint, - ExpectedNoLintLinter: expectedNolintLinter, - Replacement: i.Replacement(), - } - res = append(res, goanalysis.NewIssue(issue, pass)) - } - - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + NoLintLintName, + "Reports ill-formed or insufficient nolint directives", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runNoLintLint(pass *analysis.Pass, settings *config.NoLintLintSettings) ([]goanalysis.Issue, error) { + var needs nolintlint.Needs + if settings.RequireExplanation { + needs |= nolintlint.NeedsExplanation + } + if !settings.AllowLeadingSpace { + needs |= nolintlint.NeedsMachineOnly + } + if settings.RequireSpecific { + needs |= nolintlint.NeedsSpecific + } + if !settings.AllowUnused { + needs |= nolintlint.NeedsUnused + } + + lnt, err := nolintlint.NewLinter(needs, settings.AllowNoExplanation) + if err != nil { + return nil, err + } + + nodes := make([]ast.Node, 0, len(pass.Files)) + for _, n := range pass.Files { + nodes = append(nodes, n) + } + + lintIssues, err := lnt.Run(pass.Fset, nodes...) + if err != nil { + return nil, fmt.Errorf("linter failed to run: %s", err) + } + + var issues []goanalysis.Issue + + for _, i := range lintIssues { + expectNoLint := false + var expectedNolintLinter string + if ii, ok := i.(nolintlint.UnusedCandidate); ok { + expectedNolintLinter = ii.ExpectedLinter + expectNoLint = true + } + + issue := &result.Issue{ + FromLinter: NoLintLintName, + Text: i.Details(), + Pos: i.Position(), + ExpectNoLint: expectNoLint, + ExpectedNoLintLinter: expectedNolintLinter, + Replacement: i.Replacement(), + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) + } + + return issues, nil +} diff --git a/pkg/golinters/paralleltest.go b/pkg/golinters/paralleltest.go index 3b784baf5a21..68627961822d 100644 --- a/pkg/golinters/paralleltest.go +++ b/pkg/golinters/paralleltest.go @@ -8,14 +8,10 @@ import ( ) func NewParallelTest() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - paralleltest.NewAnalyzer(), - } - return goanalysis.NewLinter( "paralleltest", "paralleltest detects missing usage of t.Parallel() method in your Go test", - analyzers, + []*analysis.Analyzer{paralleltest.NewAnalyzer()}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/prealloc.go b/pkg/golinters/prealloc.go index 3d06cf14724f..75a9b4ec2f44 100644 --- a/pkg/golinters/prealloc.go +++ b/pkg/golinters/prealloc.go @@ -7,6 +7,7 @@ import ( "github.com/alexkohler/prealloc/pkg" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" @@ -14,44 +15,51 @@ import ( const preallocName = "prealloc" -func NewPrealloc() *goanalysis.Linter { +//nolint:dupl +func NewPreAlloc(settings *config.PreallocSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: preallocName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - preallocName, - "Finds slice declarations that could potentially be preallocated", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - s := &lintCtx.Settings().Prealloc - - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var res []goanalysis.Issue - hints := pkg.Check(pass.Files, s.Simple, s.RangeLoops, s.ForLoops) - for _, hint := range hints { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: pass.Fset.Position(hint.Pos), - Text: fmt.Sprintf("Consider preallocating %s", formatCode(hint.DeclaredSliceName, lintCtx.Cfg)), - FromLinter: preallocName, - }, pass)) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runPreAlloc(pass, settings) - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + preallocName, + "Finds slice declarations that could potentially be pre-allocated", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runPreAlloc(pass *analysis.Pass, settings *config.PreallocSettings) []goanalysis.Issue { + var issues []goanalysis.Issue + + hints := pkg.Check(pass.Files, settings.Simple, settings.RangeLoops, settings.ForLoops) + + for _, hint := range hints { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: pass.Fset.Position(hint.Pos), + Text: fmt.Sprintf("Consider pre-allocating %s", formatCode(hint.DeclaredSliceName, nil)), + FromLinter: preallocName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/promlinter.go b/pkg/golinters/promlinter.go index 4fba3d274790..f77847a49aa4 100644 --- a/pkg/golinters/promlinter.go +++ b/pkg/golinters/promlinter.go @@ -7,57 +7,71 @@ import ( "github.com/yeya24/promlinter" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewPromlinter() *goanalysis.Linter { +const promlinterName = "promlinter" + +func NewPromlinter(settings *config.PromlinterSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - const linterName = "promlinter" - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, + var promSettings promlinter.Setting + if settings != nil { + promSettings = promlinter.Setting{ + Strict: settings.Strict, + DisabledLintFuncs: settings.DisabledLinters, + } } - return goanalysis.NewLinter( - linterName, - "Check Prometheus metrics naming via promlint", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - strict := lintCtx.Cfg.LintersSettings.Promlinter.Strict - disabledLinters := lintCtx.Cfg.LintersSettings.Promlinter.DisabledLinters - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - issues := promlinter.RunLint(pass.Fset, pass.Files, promlinter.Setting{ - Strict: strict, - DisabledLintFuncs: disabledLinters, - }) + analyzer := &analysis.Analyzer{ + Name: promlinterName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runPromLinter(pass, promSettings) if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, len(issues)) - for k, i := range issues { - issue := result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("Metric: %s Error: %s", i.Metric, i.Text), - FromLinter: linterName, - } - - res[k] = goanalysis.NewIssue(&issue, pass) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + promlinterName, + "Check Prometheus metrics naming via promlint", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runPromLinter(pass *analysis.Pass, promSettings promlinter.Setting) []goanalysis.Issue { + lintIssues := promlinter.RunLint(pass.Fset, pass.Files, promSettings) + + if len(lintIssues) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, len(lintIssues)) + for k, i := range lintIssues { + issue := result.Issue{ + Pos: i.Pos, + Text: fmt.Sprintf("Metric: %s Error: %s", i.Metric, i.Text), + FromLinter: promlinterName, + } + + issues[k] = goanalysis.NewIssue(&issue, pass) + } + + return issues +} diff --git a/pkg/golinters/revive.go b/pkg/golinters/revive.go index 8fd5e9802900..30306b8ac197 100644 --- a/pkg/golinters/revive.go +++ b/pkg/golinters/revive.go @@ -7,6 +7,7 @@ import ( "go/token" "os" "reflect" + "sync" "github.com/BurntSushi/toml" reviveConfig "github.com/mgechev/revive/config" @@ -33,12 +34,15 @@ type jsonObject struct { } // NewRevive returns a new Revive linter. -func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter { - var issues []goanalysis.Issue +//nolint:dupl +func NewRevive(settings *config.ReviveSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ Name: goanalysis.TheOnlyAnalyzerName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } return goanalysis.NewLinter( @@ -48,72 +52,90 @@ func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter { nil, ).WithContextSetter(func(lintCtx *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var files []string - for _, file := range pass.Files { - files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) - } - packages := [][]string{files} - - conf, err := getReviveConfig(cfg) + issues, err := runRevive(lintCtx, pass, settings) if err != nil { return nil, err } - formatter, err := reviveConfig.GetFormatter("json") - if err != nil { - return nil, err + if len(issues) == 0 { + return nil, nil } - revive := lint.New(os.ReadFile, cfg.MaxOpenFiles) + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() - lintingRules, err := reviveConfig.GetLintingRules(conf, []lint.Rule{}) - if err != nil { - return nil, err - } + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} - failures, err := revive.Lint(packages, lintingRules, *conf) - if err != nil { - return nil, err - } +func runRevive(lintCtx *linter.Context, pass *analysis.Pass, settings *config.ReviveSettings) ([]goanalysis.Issue, error) { + var files []string + for _, file := range pass.Files { + files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) + } + packages := [][]string{files} - formatChan := make(chan lint.Failure) - exitChan := make(chan bool) + conf, err := getReviveConfig(settings) + if err != nil { + return nil, err + } - var output string - go func() { - output, err = formatter.Format(formatChan, *conf) - if err != nil { - lintCtx.Log.Errorf("Format error: %v", err) - } - exitChan <- true - }() + formatter, err := reviveConfig.GetFormatter("json") + if err != nil { + return nil, err + } - for f := range failures { - if f.Confidence < conf.Confidence { - continue - } + revive := lint.New(os.ReadFile, settings.MaxOpenFiles) - formatChan <- f - } + lintingRules, err := reviveConfig.GetLintingRules(conf, []lint.Rule{}) + if err != nil { + return nil, err + } - close(formatChan) - <-exitChan + failures, err := revive.Lint(packages, lintingRules, *conf) + if err != nil { + return nil, err + } - var results []jsonObject - err = json.Unmarshal([]byte(output), &results) - if err != nil { - return nil, err - } + formatChan := make(chan lint.Failure) + exitChan := make(chan bool) - for i := range results { - issues = append(issues, reviveToIssue(pass, &results[i])) - } + var output string + go func() { + output, err = formatter.Format(formatChan, *conf) + if err != nil { + lintCtx.Log.Errorf("Format error: %v", err) + } + exitChan <- true + }() - return nil, nil + for f := range failures { + if f.Confidence < conf.Confidence { + continue } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return issues - }).WithLoadMode(goanalysis.LoadModeSyntax) + + formatChan <- f + } + + close(formatChan) + <-exitChan + + var results []jsonObject + err = json.Unmarshal([]byte(output), &results) + if err != nil { + return nil, err + } + + var issues []goanalysis.Issue + for i := range results { + issues = append(issues, reviveToIssue(pass, &results[i])) + } + + return issues, nil } func reviveToIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue { diff --git a/pkg/golinters/rowserrcheck.go b/pkg/golinters/rowserrcheck.go index d4c89d382931..5a66d62e7dd8 100644 --- a/pkg/golinters/rowserrcheck.go +++ b/pkg/golinters/rowserrcheck.go @@ -4,20 +4,22 @@ import ( "github.com/jingyugao/rowserrcheck/passes/rowserr" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" ) -func NewRowsErrCheck() *goanalysis.Linter { - analyzer := rowserr.NewAnalyzer() +func NewRowsErrCheck(settings *config.RowsErrCheckSettings) *goanalysis.Linter { + var pkgs []string + if settings != nil { + pkgs = settings.Packages + } + + analyzer := rowserr.NewAnalyzer(pkgs...) + return goanalysis.NewLinter( "rowserrcheck", "checks whether Err of rows is checked successfully", []*analysis.Analyzer{analyzer}, nil, - ).WithLoadMode(goanalysis.LoadModeTypesInfo). - WithContextSetter(func(lintCtx *linter.Context) { - pkgs := lintCtx.Settings().RowsErrCheck.Packages - analyzer.Run = rowserr.NewRun(pkgs...) - }) + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/scopelint.go b/pkg/golinters/scopelint.go index ba3921e1964e..4a2b1bf45ac1 100644 --- a/pkg/golinters/scopelint.go +++ b/pkg/golinters/scopelint.go @@ -15,6 +15,7 @@ import ( const scopelintName = "scopelint" +//nolint:dupl func NewScopelint() *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -22,43 +23,53 @@ func NewScopelint() *goanalysis.Linter { analyzer := &analysis.Analyzer{ Name: scopelintName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - scopelintName, - "Scopelint checks for unpinned variables in go programs", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var res []result.Issue - for _, file := range pass.Files { - n := Node{ - fset: pass.Fset, - DangerObjects: map[*ast.Object]int{}, - UnsafeObjects: map[*ast.Object]int{}, - SkipFuncs: map[*ast.FuncLit]int{}, - issues: &res, - } - ast.Walk(&n, file) - } + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runScopeLint(pass) - if len(res) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - for i := range res { - resIssues = append(resIssues, goanalysis.NewIssue(&res[i], pass)) - } + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + }, + } + + return goanalysis.NewLinter( + scopelintName, + "Scopelint checks for unpinned variables in go programs", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } +func runScopeLint(pass *analysis.Pass) []goanalysis.Issue { + var lintIssues []result.Issue + + for _, file := range pass.Files { + n := Node{ + fset: pass.Fset, + DangerObjects: map[*ast.Object]int{}, + UnsafeObjects: map[*ast.Object]int{}, + SkipFuncs: map[*ast.FuncLit]int{}, + issues: &lintIssues, + } + ast.Walk(&n, file) + } + + var issues []goanalysis.Issue + for i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) + } + + return issues +} + // The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e // Node represents a Node being linted. diff --git a/pkg/golinters/sqlclosecheck.go b/pkg/golinters/sqlclosecheck.go index 48ca246e70a2..ff2c0c08ff86 100644 --- a/pkg/golinters/sqlclosecheck.go +++ b/pkg/golinters/sqlclosecheck.go @@ -8,14 +8,12 @@ import ( ) func NewSQLCloseCheck() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - analyzer.NewAnalyzer(), - } - return goanalysis.NewLinter( "sqlclosecheck", "Checks that sql.Rows and sql.Stmt are closed.", - analyzers, + []*analysis.Analyzer{ + analyzer.NewAnalyzer(), + }, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/structcheck.go b/pkg/golinters/structcheck.go index 7c16f8ec366f..fe49b1be20f9 100644 --- a/pkg/golinters/structcheck.go +++ b/pkg/golinters/structcheck.go @@ -1,4 +1,4 @@ -package golinters // nolint:dupl +package golinters import ( "fmt" @@ -7,49 +7,65 @@ import ( structcheckAPI "github.com/golangci/check/cmd/structcheck" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewStructcheck() *goanalysis.Linter { - const linterName = "structcheck" +const structcheckName = "structcheck" + +//nolint:dupl +func NewStructcheck(settings *config.StructCheckSettings) *goanalysis.Linter { var mu sync.Mutex - var res []goanalysis.Issue + var resIssues []goanalysis.Issue + analyzer := &analysis.Analyzer{ - Name: linterName, + Name: structcheckName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - linterName, - "Finds unused struct fields", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - checkExported := lintCtx.Settings().Structcheck.CheckExportedFields - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - prog := goanalysis.MakeFakeLoaderProgram(pass) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runStructCheck(pass, settings) - structcheckIssues := structcheckAPI.Run(prog, checkExported) - if len(structcheckIssues) == 0 { + if len(issues) == 0 { return nil, nil } - issues := make([]goanalysis.Issue, 0, len(structcheckIssues)) - for _, i := range structcheckIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, lintCtx.Cfg)), - FromLinter: linterName, - }, pass)) - } - mu.Lock() - res = append(res, issues...) + resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return res + }, + } + + return goanalysis.NewLinter( + structcheckName, + "Finds unused struct fields", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +//nolint:dupl +func runStructCheck(pass *analysis.Pass, settings *config.StructCheckSettings) []goanalysis.Issue { + prog := goanalysis.MakeFakeLoaderProgram(pass) + + lintIssues := structcheckAPI.Run(prog, settings.CheckExportedFields) + if len(lintIssues) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, 0, len(lintIssues)) + + for _, i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: i.Pos, + Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, nil)), + FromLinter: structcheckName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/tagliatelle.go b/pkg/golinters/tagliatelle.go index 5f58fc1d3ba9..275670e10da0 100644 --- a/pkg/golinters/tagliatelle.go +++ b/pkg/golinters/tagliatelle.go @@ -25,6 +25,10 @@ func NewTagliatelle(settings *config.TagliatelleSettings) *goanalysis.Linter { a := tagliatelle.New(cfg) - return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). - WithLoadMode(goanalysis.LoadModeSyntax) + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/tenv.go b/pkg/golinters/tenv.go index 8e3e9c61f601..174b0dd61565 100644 --- a/pkg/golinters/tenv.go +++ b/pkg/golinters/tenv.go @@ -11,10 +11,6 @@ import ( func NewTenv(settings *config.TenvSettings) *goanalysis.Linter { a := tenv.Analyzer - analyzers := []*analysis.Analyzer{ - a, - } - var cfg map[string]map[string]interface{} if settings != nil { cfg = map[string]map[string]interface{}{ @@ -27,7 +23,7 @@ func NewTenv(settings *config.TenvSettings) *goanalysis.Linter { return goanalysis.NewLinter( a.Name, a.Doc, - analyzers, + []*analysis.Analyzer{a}, cfg, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/testpackage.go b/pkg/golinters/testpackage.go index 1248e78fdac4..836403826c14 100644 --- a/pkg/golinters/testpackage.go +++ b/pkg/golinters/testpackage.go @@ -10,6 +10,7 @@ import ( func NewTestpackage(cfg *config.TestpackageSettings) *goanalysis.Linter { var a = testpackage.NewAnalyzer() + var settings map[string]map[string]interface{} if cfg != nil { settings = map[string]map[string]interface{}{ @@ -18,6 +19,7 @@ func NewTestpackage(cfg *config.TestpackageSettings) *goanalysis.Linter { }, } } + return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, settings). WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/tparallel.go b/pkg/golinters/tparallel.go index a4b96eb73538..cbe97516c49f 100644 --- a/pkg/golinters/tparallel.go +++ b/pkg/golinters/tparallel.go @@ -8,14 +8,10 @@ import ( ) func NewTparallel() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - tparallel.Analyzer, - } - return goanalysis.NewLinter( "tparallel", "tparallel detects inappropriate usage of t.Parallel() method in your Go test codes", - analyzers, + []*analysis.Analyzer{tparallel.Analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/typecheck.go b/pkg/golinters/typecheck.go index 24f4339fbd3f..e9b26ef485e1 100644 --- a/pkg/golinters/typecheck.go +++ b/pkg/golinters/typecheck.go @@ -12,17 +12,13 @@ func NewTypecheck() *goanalysis.Linter { analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (interface{}, error) { - return nil, nil - }, + Run: goanalysis.DummyRun, } - linter := goanalysis.NewLinter( + return goanalysis.NewLinter( linterName, "Like the front-end of a Go compiler, parses and type-checks Go code", []*analysis.Analyzer{analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) - - return linter } diff --git a/pkg/golinters/unconvert.go b/pkg/golinters/unconvert.go index 456f6836cc1d..def9f156578c 100644 --- a/pkg/golinters/unconvert.go +++ b/pkg/golinters/unconvert.go @@ -11,43 +11,57 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +const unconvertName = "unconvert" + +//nolint:dupl func NewUnconvert() *goanalysis.Linter { - const linterName = "unconvert" var mu sync.Mutex - var res []goanalysis.Issue + var resIssues []goanalysis.Issue + analyzer := &analysis.Analyzer{ - Name: linterName, + Name: unconvertName, Doc: goanalysis.TheOnlyanalyzerDoc, - } - return goanalysis.NewLinter( - linterName, - "Remove unnecessary type conversions", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - prog := goanalysis.MakeFakeLoaderProgram(pass) + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runUnconvert(pass) - positions := unconvertAPI.Run(prog) - if len(positions) == 0 { + if len(issues) == 0 { return nil, nil } - issues := make([]goanalysis.Issue, 0, len(positions)) - for _, pos := range positions { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: pos, - Text: "unnecessary conversion", - FromLinter: linterName, - }, pass)) - } - mu.Lock() - res = append(res, issues...) + resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return res + }, + } + + return goanalysis.NewLinter( + unconvertName, + "Remove unnecessary type conversions", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +func runUnconvert(pass *analysis.Pass) []goanalysis.Issue { + prog := goanalysis.MakeFakeLoaderProgram(pass) + + positions := unconvertAPI.Run(prog) + if len(positions) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, 0, len(positions)) + for _, pos := range positions { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: pos, + Text: "unnecessary conversion", + FromLinter: unconvertName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/unparam.go b/pkg/golinters/unparam.go index 33dd55c9b284..7accf29566cc 100644 --- a/pkg/golinters/unparam.go +++ b/pkg/golinters/unparam.go @@ -8,69 +8,83 @@ import ( "golang.org/x/tools/go/packages" "mvdan.cc/unparam/check" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewUnparam() *goanalysis.Linter { - const linterName = "unparam" +const unparamName = "unparam" + +func NewUnparam(settings *config.UnparamSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: linterName, + Name: unparamName, Doc: goanalysis.TheOnlyanalyzerDoc, Requires: []*analysis.Analyzer{buildssa.Analyzer}, - } - return goanalysis.NewLinter( - linterName, - "Reports unused function parameters", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - us := &lintCtx.Settings().Unparam - if us.Algo != "cha" { - lintCtx.Log.Warnf("`linters-settings.unparam.algo` isn't supported by the newest `unparam`") - } - - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) - ssaPkg := ssa.Pkg - - pkg := &packages.Package{ - Fset: pass.Fset, - Syntax: pass.Files, - Types: pass.Pkg, - TypesInfo: pass.TypesInfo, - } - - c := &check.Checker{} - c.CheckExportedFuncs(us.CheckExported) - c.Packages([]*packages.Package{pkg}) - c.ProgramSSA(ssaPkg.Prog) - - unparamIssues, err := c.Check() + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runUnparam(pass, settings) if err != nil { return nil, err } - var res []goanalysis.Issue - for _, i := range unparamIssues { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: pass.Fset.Position(i.Pos()), - Text: i.Message(), - FromLinter: linterName, - }, pass)) + if len(issues) == 0 { + return nil, nil } mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil + }, + } + + return goanalysis.NewLinter( + unparamName, + "Reports unused function parameters", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + if settings.Algo != "cha" { + lintCtx.Log.Warnf("`linters-settings.unparam.algo` isn't supported by the newest `unparam`") } }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +func runUnparam(pass *analysis.Pass, settings *config.UnparamSettings) ([]goanalysis.Issue, error) { + ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) + ssaPkg := ssa.Pkg + + pkg := &packages.Package{ + Fset: pass.Fset, + Syntax: pass.Files, + Types: pass.Pkg, + TypesInfo: pass.TypesInfo, + } + + c := &check.Checker{} + c.CheckExportedFuncs(settings.CheckExported) + c.Packages([]*packages.Package{pkg}) + c.ProgramSSA(ssaPkg.Prog) + + unparamIssues, err := c.Check() + if err != nil { + return nil, err + } + + var issues []goanalysis.Issue + for _, i := range unparamIssues { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: pass.Fset.Position(i.Pos()), + Text: i.Message(), + FromLinter: unparamName, + }, pass)) + } + + return issues, nil +} diff --git a/pkg/golinters/unused.go b/pkg/golinters/unused.go index 13e05a5085ba..35b4360119ca 100644 --- a/pkg/golinters/unused.go +++ b/pkg/golinters/unused.go @@ -13,56 +13,28 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +const unusedName = "unused" + type UnusedSettings struct { GoVersion string } func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { - const name = "unused" - var mu sync.Mutex var resIssues []goanalysis.Issue analyzer := &analysis.Analyzer{ - Name: name, + Name: unusedName, Doc: unused.Analyzer.Analyzer.Doc, Requires: unused.Analyzer.Analyzer.Requires, Run: func(pass *analysis.Pass) (interface{}, error) { - res, err := unused.Analyzer.Analyzer.Run(pass) + issues, err := runUnused(pass) if err != nil { return nil, err } - sr := unused.Serialize(pass, res.(unused.Result), pass.Fset) - - used := make(map[string]bool) - for _, obj := range sr.Used { - used[fmt.Sprintf("%s %d %s", obj.Position.Filename, obj.Position.Line, obj.Name)] = true - } - - var issues []goanalysis.Issue - // Inspired by https://github.com/dominikh/go-tools/blob/d694aadcb1f50c2d8ac0a1dd06217ebb9f654764/lintcmd/lint.go#L177-L197 - for _, object := range sr.Unused { - if object.Kind == "type param" { - continue - } - - if object.InGenerated { - continue - } - - key := fmt.Sprintf("%s %d %s", object.Position.Filename, object.Position.Line, object.Name) - if used[key] { - continue - } - - issue := goanalysis.NewIssue(&result.Issue{ - FromLinter: name, - Text: fmt.Sprintf("%s %s is unused", object.Kind, object.Name), - Pos: object.Position, - }, pass) - - issues = append(issues, issue) + if len(issues) == 0 { + return nil, nil } mu.Lock() @@ -75,14 +47,54 @@ func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { setAnalyzerGoVersion(analyzer, getGoVersion(settings)) - lnt := goanalysis.NewLinter( - name, + return goanalysis.NewLinter( + unusedName, "Checks Go code for unused constants, variables, functions and types", []*analysis.Analyzer{analyzer}, nil, ).WithIssuesReporter(func(lintCtx *linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} + +func runUnused(pass *analysis.Pass) ([]goanalysis.Issue, error) { + res, err := unused.Analyzer.Analyzer.Run(pass) + if err != nil { + return nil, err + } + + sr := unused.Serialize(pass, res.(unused.Result), pass.Fset) + + used := make(map[string]bool) + for _, obj := range sr.Used { + used[fmt.Sprintf("%s %d %s", obj.Position.Filename, obj.Position.Line, obj.Name)] = true + } + + var issues []goanalysis.Issue + + // Inspired by https://github.com/dominikh/go-tools/blob/d694aadcb1f50c2d8ac0a1dd06217ebb9f654764/lintcmd/lint.go#L177-L197 + for _, object := range sr.Unused { + if object.Kind == "type param" { + continue + } + + if object.InGenerated { + continue + } + + key := fmt.Sprintf("%s %d %s", object.Position.Filename, object.Position.Line, object.Name) + if used[key] { + continue + } + + issue := goanalysis.NewIssue(&result.Issue{ + FromLinter: unusedName, + Text: fmt.Sprintf("%s %s is unused", object.Kind, object.Name), + Pos: object.Position, + }, pass) + + issues = append(issues, issue) + } - return lnt + return issues, nil } diff --git a/pkg/golinters/varcheck.go b/pkg/golinters/varcheck.go index dcf2e7de8e6a..c2c5b7aa9c35 100644 --- a/pkg/golinters/varcheck.go +++ b/pkg/golinters/varcheck.go @@ -1,4 +1,4 @@ -package golinters // nolint:dupl +package golinters import ( "fmt" @@ -7,49 +7,66 @@ import ( varcheckAPI "github.com/golangci/check/cmd/varcheck" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewVarcheck() *goanalysis.Linter { - const linterName = "varcheck" +const varcheckName = "varcheck" + +func NewVarcheck(settings *config.VarCheckSettings) *goanalysis.Linter { var mu sync.Mutex - var res []goanalysis.Issue + var resIssues []goanalysis.Issue + analyzer := &analysis.Analyzer{ - Name: linterName, + Name: varcheckName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( - linterName, + varcheckName, "Finds unused global variables and constants", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - checkExported := lintCtx.Settings().Varcheck.CheckExportedFields analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - prog := goanalysis.MakeFakeLoaderProgram(pass) + issues := runVarCheck(pass, settings) - varcheckIssues := varcheckAPI.Run(prog, checkExported) - if len(varcheckIssues) == 0 { + if len(issues) == 0 { return nil, nil } - issues := make([]goanalysis.Issue, 0, len(varcheckIssues)) - for _, i := range varcheckIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, lintCtx.Cfg)), - FromLinter: linterName, - }, pass)) - } - mu.Lock() - res = append(res, issues...) + resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil } }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return res + return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } + +//nolint:dupl +func runVarCheck(pass *analysis.Pass, settings *config.VarCheckSettings) []goanalysis.Issue { + prog := goanalysis.MakeFakeLoaderProgram(pass) + + lintIssues := varcheckAPI.Run(prog, settings.CheckExportedFields) + if len(lintIssues) == 0 { + return nil + } + + issues := make([]goanalysis.Issue, 0, len(lintIssues)) + + for _, i := range lintIssues { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Pos: i.Pos, + Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, nil)), + FromLinter: varcheckName, + }, pass)) + } + + return issues +} diff --git a/pkg/golinters/wastedassign.go b/pkg/golinters/wastedassign.go index d359fb019534..92798d4f7353 100644 --- a/pkg/golinters/wastedassign.go +++ b/pkg/golinters/wastedassign.go @@ -8,14 +8,10 @@ import ( ) func NewWastedAssign() *goanalysis.Linter { - analyzers := []*analysis.Analyzer{ - wastedassign.Analyzer, - } - return goanalysis.NewLinter( "wastedassign", "wastedassign finds wasted assignment statements.", - analyzers, + []*analysis.Analyzer{wastedassign.Analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/whitespace.go b/pkg/golinters/whitespace.go index d475465a2118..1b32a7ad63ac 100644 --- a/pkg/golinters/whitespace.go +++ b/pkg/golinters/whitespace.go @@ -8,73 +8,51 @@ import ( "github.com/ultraware/whitespace" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewWhitespace() *goanalysis.Linter { - const linterName = "whitespace" +const whitespaceName = "whitespace" + +//nolint:dupl +func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue + var wsSettings whitespace.Settings + if settings != nil { + wsSettings = whitespace.Settings{ + MultiIf: settings.MultiIf, + MultiFunc: settings.MultiFunc, + } + } + analyzer := &analysis.Analyzer{ - Name: linterName, + Name: whitespaceName, Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, } + return goanalysis.NewLinter( - linterName, + whitespaceName, "Tool for detection of leading and trailing whitespace", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { - cfg := lintCtx.Cfg.LintersSettings.Whitespace - settings := whitespace.Settings{MultiIf: cfg.MultiIf, MultiFunc: cfg.MultiFunc} - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var issues []whitespace.Message - for _, file := range pass.Files { - issues = append(issues, whitespace.Run(file, pass.Fset, settings)...) + issues, err := runWhitespace(lintCtx, pass, wsSettings) + if err != nil { + return nil, err } if len(issues) == 0 { return nil, nil } - res := make([]goanalysis.Issue, len(issues)) - for k, i := range issues { - issue := result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - LineRange: &result.Range{From: i.Pos.Line, To: i.Pos.Line}, - Text: i.Message, - FromLinter: linterName, - Replacement: &result.Replacement{}, - } - - bracketLine, err := lintCtx.LineCache.GetLine(issue.Pos.Filename, issue.Pos.Line) - if err != nil { - return nil, errors.Wrapf(err, "failed to get line %s:%d", issue.Pos.Filename, issue.Pos.Line) - } - - switch i.Type { - case whitespace.MessageTypeLeading: - issue.LineRange.To++ // cover two lines by the issue: opening bracket "{" (issue.Pos.Line) and following empty line - case whitespace.MessageTypeTrailing: - issue.LineRange.From-- // cover two lines by the issue: closing bracket "}" (issue.Pos.Line) and preceding empty line - issue.Pos.Line-- // set in sync with LineRange.From to not break fixer and other code features - case whitespace.MessageTypeAddAfter: - bracketLine += "\n" - } - issue.Replacement.NewLines = []string{bracketLine} - - res[k] = goanalysis.NewIssue(&issue, pass) - } - mu.Lock() - resIssues = append(resIssues, res...) + resIssues = append(resIssues, issues...) mu.Unlock() return nil, nil @@ -83,3 +61,48 @@ func NewWhitespace() *goanalysis.Linter { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runWhitespace(lintCtx *linter.Context, pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { + var messages []whitespace.Message + for _, file := range pass.Files { + messages = append(messages, whitespace.Run(file, pass.Fset, wsSettings)...) + } + + if len(messages) == 0 { + return nil, nil + } + + issues := make([]goanalysis.Issue, len(messages)) + for k, i := range messages { + issue := result.Issue{ + Pos: token.Position{ + Filename: i.Pos.Filename, + Line: i.Pos.Line, + }, + LineRange: &result.Range{From: i.Pos.Line, To: i.Pos.Line}, + Text: i.Message, + FromLinter: whitespaceName, + Replacement: &result.Replacement{}, + } + + bracketLine, err := lintCtx.LineCache.GetLine(issue.Pos.Filename, issue.Pos.Line) + if err != nil { + return nil, errors.Wrapf(err, "failed to get line %s:%d", issue.Pos.Filename, issue.Pos.Line) + } + + switch i.Type { + case whitespace.MessageTypeLeading: + issue.LineRange.To++ // cover two lines by the issue: opening bracket "{" (issue.Pos.Line) and following empty line + case whitespace.MessageTypeTrailing: + issue.LineRange.From-- // cover two lines by the issue: closing bracket "}" (issue.Pos.Line) and preceding empty line + issue.Pos.Line-- // set in sync with LineRange.From to not break fixer and other code features + case whitespace.MessageTypeAddAfter: + bracketLine += "\n" + } + issue.Replacement.NewLines = []string{bracketLine} + + issues[k] = goanalysis.NewIssue(&issue, pass) + } + + return issues, nil +} diff --git a/pkg/golinters/wsl.go b/pkg/golinters/wsl.go index b5961cc1f2f8..0b10798eb629 100644 --- a/pkg/golinters/wsl.go +++ b/pkg/golinters/wsl.go @@ -6,78 +6,89 @@ import ( "github.com/bombsimon/wsl/v3" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -const ( - name = "wsl" -) +const wslName = "wsl" // NewWSL returns a new WSL linter. -func NewWSL() *goanalysis.Linter { - var ( - issues []goanalysis.Issue - mu = sync.Mutex{} - analyzer = &analysis.Analyzer{ - Name: goanalysis.TheOnlyAnalyzerName, - Doc: goanalysis.TheOnlyanalyzerDoc, - } - ) +func NewWSL(settings *config.WSLSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue - return goanalysis.NewLinter( - name, - "Whitespace Linter - Forces you to use empty lines!", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { - var ( - files = make([]string, 0, len(pass.Files)) - linterCfg = lintCtx.Cfg.LintersSettings.WSL - processorCfg = wsl.Configuration{ - StrictAppend: linterCfg.StrictAppend, - AllowAssignAndCallCuddle: linterCfg.AllowAssignAndCallCuddle, - AllowAssignAndAnythingCuddle: linterCfg.AllowAssignAndAnythingCuddle, - AllowMultiLineAssignCuddle: linterCfg.AllowMultiLineAssignCuddle, - AllowCuddleDeclaration: linterCfg.AllowCuddleDeclaration, - AllowTrailingComment: linterCfg.AllowTrailingComment, - AllowSeparatedLeadingComment: linterCfg.AllowSeparatedLeadingComment, - ForceCuddleErrCheckAndAssign: linterCfg.ForceCuddleErrCheckAndAssign, - ForceCaseTrailingWhitespaceLimit: linterCfg.ForceCaseTrailingWhitespaceLimit, - ForceExclusiveShortDeclarations: linterCfg.ForceExclusiveShortDeclarations, - AllowCuddleWithCalls: []string{"Lock", "RLock"}, - AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, - ErrorVariableNames: []string{"err"}, - } - ) - - for _, file := range pass.Files { - files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) - } + conf := &wsl.Configuration{ + AllowCuddleWithCalls: []string{"Lock", "RLock"}, + AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, + ErrorVariableNames: []string{"err"}, + } + + if settings != nil { + conf.StrictAppend = settings.StrictAppend + conf.AllowAssignAndCallCuddle = settings.AllowAssignAndCallCuddle + conf.AllowAssignAndAnythingCuddle = settings.AllowAssignAndAnythingCuddle + conf.AllowMultiLineAssignCuddle = settings.AllowMultiLineAssignCuddle + conf.AllowCuddleDeclaration = settings.AllowCuddleDeclaration + conf.AllowTrailingComment = settings.AllowTrailingComment + conf.AllowSeparatedLeadingComment = settings.AllowSeparatedLeadingComment + conf.ForceCuddleErrCheckAndAssign = settings.ForceCuddleErrCheckAndAssign + conf.ForceCaseTrailingWhitespaceLimit = settings.ForceCaseTrailingWhitespaceLimit + conf.ForceExclusiveShortDeclarations = settings.ForceExclusiveShortDeclarations + } - wslErrors, _ := wsl.NewProcessorWithConfig(processorCfg). - ProcessFiles(files) + analyzer := &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues := runWSL(pass, conf) - if len(wslErrors) == 0 { + if len(issues) == 0 { return nil, nil } mu.Lock() - defer mu.Unlock() - - for _, err := range wslErrors { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - FromLinter: name, - Pos: err.Position, - Text: err.Reason, - }, pass)) - } + resIssues = append(resIssues, issues...) + mu.Unlock() return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return issues + }, + } + + return goanalysis.NewLinter( + wslName, + "Whitespace Linter - Forces you to use empty lines!", + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } + +func runWSL(pass *analysis.Pass, conf *wsl.Configuration) []goanalysis.Issue { + var files = make([]string, 0, len(pass.Files)) + for _, file := range pass.Files { + files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) + } + + if conf == nil { + return nil + } + + wslErrors, _ := wsl.NewProcessorWithConfig(*conf).ProcessFiles(files) + if len(wslErrors) == 0 { + return nil + } + + var issues []goanalysis.Issue + for _, err := range wslErrors { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + FromLinter: wslName, + Pos: err.Position, + Text: err.Reason, + }, pass)) + } + + return issues +} diff --git a/pkg/lint/linter/config.go b/pkg/lint/linter/config.go index c8e065542e2a..167ac462594c 100644 --- a/pkg/lint/linter/config.go +++ b/pkg/lint/linter/config.go @@ -66,7 +66,7 @@ func (lc *Config) WithLoadFiles() *Config { func (lc *Config) WithLoadForGoAnalysis() *Config { lc = lc.WithLoadFiles() - lc.LoadMode |= packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedTypesSizes + lc.LoadMode |= packages.NeedImports | packages.NeedDeps | packages.NeedExportFile | packages.NeedTypesSizes lc.IsSlow = true return lc } diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 3bc559a78303..fbfb5fa68bd4 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -100,51 +100,104 @@ func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config) //nolint:funlen func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { - var bidichkCfg *config.BiDiChkSettings - var cyclopCfg *config.Cyclop - var decorderCfg *config.DecorderSettings - var errchkjsonCfg *config.ErrChkJSONSettings - var errorlintCfg *config.ErrorLintSettings - var exhaustiveCfg *config.ExhaustiveSettings - var exhaustiveStructCfg *config.ExhaustiveStructSettings - var exhaustructCfg *config.ExhaustructSettings - var gciCfg *config.GciSettings - var goModDirectivesCfg *config.GoModDirectivesSettings - var goMndCfg *config.GoMndSettings - var gosecCfg *config.GoSecSettings - var gosimpleCfg *config.StaticCheckSettings - var govetCfg *config.GovetSettings - var grouperCfg *config.GrouperSettings - var ifshortCfg *config.IfshortSettings - var importAsCfg *config.ImportAsSettings - var ireturnCfg *config.IreturnSettings - var maintIdxCfg *config.MaintIdxSettings - var nilNilCfg *config.NilNilSettings - var nlreturnCfg *config.NlreturnSettings - var predeclaredCfg *config.PredeclaredSettings - var reviveCfg *config.ReviveSettings - var staticcheckCfg *config.StaticCheckSettings - var stylecheckCfg *config.StaticCheckSettings - var tagliatelleCfg *config.TagliatelleSettings - var tenvCfg *config.TenvSettings - var testpackageCfg *config.TestpackageSettings - var thelperCfg *config.ThelperSettings - var unusedCfg *config.StaticCheckSettings - var varnamelenCfg *config.VarnamelenSettings - var wrapcheckCfg *config.WrapcheckSettings + var ( + bidichkCfg *config.BiDiChkSettings + cyclopCfg *config.Cyclop + decorderCfg *config.DecorderSettings + depGuardCfg *config.DepGuardSettings + dogsledCfg *config.DogsledSettings + duplCfg *config.DuplSettings + errcheckCfg *config.ErrcheckSettings + errchkjsonCfg *config.ErrChkJSONSettings + errorlintCfg *config.ErrorLintSettings + exhaustiveCfg *config.ExhaustiveSettings + exhaustiveStructCfg *config.ExhaustiveStructSettings + exhaustructCfg *config.ExhaustructSettings + forbidigoCfg *config.ForbidigoSettings + funlenCfg *config.FunlenSettings + gciCfg *config.GciSettings + gocognitCfg *config.GocognitSettings + goconstCfg *config.GoConstSettings + gocriticCfg *config.GocriticSettings + gocycloCfg *config.GoCycloSettings + godotCfg *config.GodotSettings + godoxCfg *config.GodoxSettings + gofmtCfg *config.GoFmtSettings + gofumptCfg *config.GofumptSettings + goheaderCfg *config.GoHeaderSettings + goimportsCfg *config.GoImportsSettings + golintCfg *config.GoLintSettings + goMndCfg *config.GoMndSettings + goModDirectivesCfg *config.GoModDirectivesSettings + gomodguardCfg *config.GoModGuardSettings + gosecCfg *config.GoSecSettings + gosimpleCfg *config.StaticCheckSettings + govetCfg *config.GovetSettings + grouperCfg *config.GrouperSettings + ifshortCfg *config.IfshortSettings + importAsCfg *config.ImportAsSettings + ireturnCfg *config.IreturnSettings + lllCfg *config.LllSettings + maintIdxCfg *config.MaintIdxSettings + makezeroCfg *config.MakezeroSettings + malignedCfg *config.MalignedSettings + misspellCfg *config.MisspellSettings + nakedretCfg *config.NakedretSettings + nestifCfg *config.NestifSettings + nilNilCfg *config.NilNilSettings + nlreturnCfg *config.NlreturnSettings + noLintLintCfg *config.NoLintLintSettings + preallocCfg *config.PreallocSettings + predeclaredCfg *config.PredeclaredSettings + promlinterCfg *config.PromlinterSettings + reviveCfg *config.ReviveSettings + rowserrcheckCfg *config.RowsErrCheckSettings + staticcheckCfg *config.StaticCheckSettings + structcheckCfg *config.StructCheckSettings + stylecheckCfg *config.StaticCheckSettings + tagliatelleCfg *config.TagliatelleSettings + tenvCfg *config.TenvSettings + testpackageCfg *config.TestpackageSettings + thelperCfg *config.ThelperSettings + unparamCfg *config.UnparamSettings + unusedCfg *config.StaticCheckSettings + varcheckCfg *config.VarCheckSettings + varnamelenCfg *config.VarnamelenSettings + whitespaceCfg *config.WhitespaceSettings + wrapcheckCfg *config.WrapcheckSettings + wslCfg *config.WSLSettings + ) if m.cfg != nil { bidichkCfg = &m.cfg.LintersSettings.BiDiChk cyclopCfg = &m.cfg.LintersSettings.Cyclop - errchkjsonCfg = &m.cfg.LintersSettings.ErrChkJSON decorderCfg = &m.cfg.LintersSettings.Decorder + depGuardCfg = &m.cfg.LintersSettings.Depguard + dogsledCfg = &m.cfg.LintersSettings.Dogsled + duplCfg = &m.cfg.LintersSettings.Dupl + errcheckCfg = &m.cfg.LintersSettings.Errcheck + errchkjsonCfg = &m.cfg.LintersSettings.ErrChkJSON errorlintCfg = &m.cfg.LintersSettings.ErrorLint exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct exhaustructCfg = &m.cfg.LintersSettings.Exhaustruct + forbidigoCfg = &m.cfg.LintersSettings.Forbidigo + funlenCfg = &m.cfg.LintersSettings.Funlen gciCfg = &m.cfg.LintersSettings.Gci - goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives + gocognitCfg = &m.cfg.LintersSettings.Gocognit + goconstCfg = &m.cfg.LintersSettings.Goconst + gocriticCfg = &m.cfg.LintersSettings.Gocritic + gocycloCfg = &m.cfg.LintersSettings.Gocyclo + godotCfg = &m.cfg.LintersSettings.Godot + godoxCfg = &m.cfg.LintersSettings.Godox + gofmtCfg = &m.cfg.LintersSettings.Gofmt + gofumptCfg = &m.cfg.LintersSettings.Gofumpt + goheaderCfg = &m.cfg.LintersSettings.Goheader + goimportsCfg = &m.cfg.LintersSettings.Goimports + golintCfg = &m.cfg.LintersSettings.Golint goMndCfg = &m.cfg.LintersSettings.Gomnd + goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives + gomodguardCfg = &m.cfg.LintersSettings.Gomodguard gosecCfg = &m.cfg.LintersSettings.Gosec gosimpleCfg = &m.cfg.LintersSettings.Gosimple govetCfg = &m.cfg.LintersSettings.Govet @@ -152,20 +205,35 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { ifshortCfg = &m.cfg.LintersSettings.Ifshort importAsCfg = &m.cfg.LintersSettings.ImportAs ireturnCfg = &m.cfg.LintersSettings.Ireturn + lllCfg = &m.cfg.LintersSettings.Lll maintIdxCfg = &m.cfg.LintersSettings.MaintIdx + makezeroCfg = &m.cfg.LintersSettings.Makezero + malignedCfg = &m.cfg.LintersSettings.Maligned + misspellCfg = &m.cfg.LintersSettings.Misspell + nakedretCfg = &m.cfg.LintersSettings.Nakedret + nestifCfg = &m.cfg.LintersSettings.Nestif nilNilCfg = &m.cfg.LintersSettings.NilNil nlreturnCfg = &m.cfg.LintersSettings.Nlreturn + noLintLintCfg = &m.cfg.LintersSettings.NoLintLint + preallocCfg = &m.cfg.LintersSettings.Prealloc predeclaredCfg = &m.cfg.LintersSettings.Predeclared + promlinterCfg = &m.cfg.LintersSettings.Promlinter reviveCfg = &m.cfg.LintersSettings.Revive + rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck staticcheckCfg = &m.cfg.LintersSettings.Staticcheck + structcheckCfg = &m.cfg.LintersSettings.Structcheck stylecheckCfg = &m.cfg.LintersSettings.Stylecheck tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle tenvCfg = &m.cfg.LintersSettings.Tenv testpackageCfg = &m.cfg.LintersSettings.Testpackage thelperCfg = &m.cfg.LintersSettings.Thelper + unparamCfg = &m.cfg.LintersSettings.Unparam unusedCfg = &m.cfg.LintersSettings.Unused + varcheckCfg = &m.cfg.LintersSettings.Varcheck varnamelenCfg = &m.cfg.LintersSettings.Varnamelen + whitespaceCfg = &m.cfg.LintersSettings.Whitespace wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck + wslCfg = &m.cfg.LintersSettings.WSL if govetCfg != nil { govetCfg.Go = m.cfg.Run.Go @@ -223,17 +291,17 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetUnused). WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"), - linter.NewConfig(golinters.NewDepguard()). + linter.NewConfig(golinters.NewDepguard(depGuardCfg)). WithSince("v1.4.0"). WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). WithURL("https://github.com/OpenPeeDeeP/depguard"), - linter.NewConfig(golinters.NewDogsled()). + linter.NewConfig(golinters.NewDogsled(dogsledCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/alexkohler/dogsled"), - linter.NewConfig(golinters.NewDupl()). + linter.NewConfig(golinters.NewDupl(duplCfg)). WithSince("v1.0.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/mibk/dupl"), @@ -244,7 +312,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/charithe/durationcheck"), - linter.NewConfig(golinters.NewErrcheck()). + linter.NewConfig(golinters.NewErrcheck(errcheckCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetBugs, linter.PresetError). @@ -299,7 +367,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/kyoh86/exportloopref"), - linter.NewConfig(golinters.NewForbidigo()). + linter.NewConfig(golinters.NewForbidigo(forbidigoCfg)). WithSince("v1.34.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/ashanbrown/forbidigo"), @@ -309,7 +377,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/gostaticanalysis/forcetypeassert"), - linter.NewConfig(golinters.NewFunlen()). + linter.NewConfig(golinters.NewFunlen(funlenCfg)). WithSince("v1.18.0"). WithPresets(linter.PresetComplexity). WithURL("https://github.com/ultraware/funlen"), @@ -329,34 +397,34 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/leighmcculloch/gochecknoinits"), - linter.NewConfig(golinters.NewGocognit()). + linter.NewConfig(golinters.NewGocognit(gocognitCfg)). WithSince("v1.20.0"). WithPresets(linter.PresetComplexity). WithURL("https://github.com/uudashr/gocognit"), - linter.NewConfig(golinters.NewGoconst()). + linter.NewConfig(golinters.NewGoconst(goconstCfg)). WithSince("v1.0.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/jgautheron/goconst"), - linter.NewConfig(golinters.NewGocritic()). + linter.NewConfig(golinters.NewGocritic(gocriticCfg, m.cfg)). WithSince("v1.12.0"). WithPresets(linter.PresetStyle, linter.PresetMetaLinter). WithLoadForGoAnalysis(). WithURL("https://github.com/go-critic/go-critic"), - linter.NewConfig(golinters.NewGocyclo()). + linter.NewConfig(golinters.NewGocyclo(gocycloCfg)). WithSince("v1.0.0"). WithPresets(linter.PresetComplexity). WithURL("https://github.com/fzipp/gocyclo"), - linter.NewConfig(golinters.NewGodot()). + linter.NewConfig(golinters.NewGodot(godotCfg)). WithSince("v1.25.0"). WithPresets(linter.PresetStyle, linter.PresetComment). WithAutoFix(). WithURL("https://github.com/tetafro/godot"), - linter.NewConfig(golinters.NewGodox()). + linter.NewConfig(golinters.NewGodox(godoxCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle, linter.PresetComment). WithURL("https://github.com/matoous/godox"), @@ -367,30 +435,30 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/Djarvur/go-err113"), - linter.NewConfig(golinters.NewGofmt()). + linter.NewConfig(golinters.NewGofmt(gofmtCfg)). WithSince("v1.0.0"). WithPresets(linter.PresetFormatting). WithAutoFix(). WithURL("https://golang.org/cmd/gofmt/"), - linter.NewConfig(golinters.NewGofumpt()). + linter.NewConfig(golinters.NewGofumpt(gofumptCfg)). WithSince("v1.28.0"). WithPresets(linter.PresetFormatting). WithAutoFix(). WithURL("https://github.com/mvdan/gofumpt"), - linter.NewConfig(golinters.NewGoHeader()). + linter.NewConfig(golinters.NewGoHeader(goheaderCfg)). WithSince("v1.28.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/denis-tingaikin/go-header"), - linter.NewConfig(golinters.NewGoimports()). + linter.NewConfig(golinters.NewGoimports(goimportsCfg)). WithSince("v1.20.0"). WithPresets(linter.PresetFormatting, linter.PresetImport). WithAutoFix(). WithURL("https://godoc.org/golang.org/x/tools/cmd/goimports"), - linter.NewConfig(golinters.NewGolint()). + linter.NewConfig(golinters.NewGolint(golintCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). @@ -407,7 +475,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetModule). WithURL("https://github.com/ldez/gomoddirectives"), - linter.NewConfig(golinters.NewGomodguard()). + linter.NewConfig(golinters.NewGomodguard(gomodguardCfg)). WithSince("v1.25.0"). WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). WithURL("https://github.com/ryancurrah/gomodguard"), @@ -473,7 +541,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/butuzov/ireturn"), - linter.NewConfig(golinters.NewLLL()). + linter.NewConfig(golinters.NewLLL(lllCfg)). WithSince("v1.8.0"). WithPresets(linter.PresetStyle), @@ -482,31 +550,31 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetComplexity). WithURL("https://github.com/yagipy/maintidx"), - linter.NewConfig(golinters.NewMakezero()). + linter.NewConfig(golinters.NewMakezero(makezeroCfg)). WithSince("v1.34.0"). WithPresets(linter.PresetStyle, linter.PresetBugs). WithLoadForGoAnalysis(). WithURL("https://github.com/ashanbrown/makezero"), - linter.NewConfig(golinters.NewMaligned()). + linter.NewConfig(golinters.NewMaligned(malignedCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetPerformance). WithURL("https://github.com/mdempsky/maligned"). Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", "govet 'fieldalignment'"), - linter.NewConfig(golinters.NewMisspell()). + linter.NewConfig(golinters.NewMisspell(misspellCfg)). WithSince("v1.8.0"). WithPresets(linter.PresetStyle, linter.PresetComment). WithAutoFix(). WithURL("https://github.com/client9/misspell"), - linter.NewConfig(golinters.NewNakedret()). + linter.NewConfig(golinters.NewNakedret(nakedretCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/alexkohler/nakedret"), - linter.NewConfig(golinters.NewNestif()). + linter.NewConfig(golinters.NewNestif(nestifCfg)). WithSince("v1.25.0"). WithPresets(linter.PresetComplexity). WithURL("https://github.com/nakabonne/nestif"), @@ -551,7 +619,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetTest). WithURL("https://github.com/kunwardeep/paralleltest"), - linter.NewConfig(golinters.NewPrealloc()). + linter.NewConfig(golinters.NewPreAlloc(preallocCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetPerformance). WithURL("https://github.com/alexkohler/prealloc"), @@ -561,7 +629,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/nishanths/predeclared"), - linter.NewConfig(golinters.NewPromlinter()). + linter.NewConfig(golinters.NewPromlinter(promlinterCfg)). WithSince("v1.40.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/yeya24/promlinter"), @@ -572,7 +640,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { ConsiderSlow(). WithURL("https://github.com/mgechev/revive"), - linter.NewConfig(golinters.NewRowsErrCheck()). + linter.NewConfig(golinters.NewRowsErrCheck(rowserrcheckCfg)). WithSince("v1.23.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetBugs, linter.PresetSQL). @@ -599,7 +667,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithAlternativeNames(megacheckName). WithURL("https://staticcheck.io/"), - linter.NewConfig(golinters.NewStructcheck()). + linter.NewConfig(golinters.NewStructcheck(structcheckCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetUnused). @@ -653,7 +721,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/mdempsky/unconvert"), - linter.NewConfig(golinters.NewUnparam()). + linter.NewConfig(golinters.NewUnparam(unparamCfg)). WithSince("v1.9.0"). WithPresets(linter.PresetUnused). WithLoadForGoAnalysis(). @@ -669,7 +737,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithChangeTypes(). WithURL("https://github.com/dominikh/go-tools/tree/master/unused"), - linter.NewConfig(golinters.NewVarcheck()). + linter.NewConfig(golinters.NewVarcheck(varcheckCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetUnused). @@ -688,7 +756,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithURL("https://github.com/sanposhiho/wastedassign"). WithNoopFallback(m.cfg), - linter.NewConfig(golinters.NewWhitespace()). + linter.NewConfig(golinters.NewWhitespace(whitespaceCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle). WithAutoFix(). @@ -700,7 +768,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/tomarrell/wrapcheck"), - linter.NewConfig(golinters.NewWSL()). + linter.NewConfig(golinters.NewWSL(wslCfg)). WithSince("v1.20.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/bombsimon/wsl"), @@ -709,7 +777,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives - linter.NewConfig(golinters.NewNoLintLint()). + linter.NewConfig(golinters.NewNoLintLint(noLintLintCfg)). WithSince("v1.26.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint/README.md"), @@ -717,12 +785,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { enabledByDefault := map[string]bool{ golinters.NewGovet(nil).Name(): true, - golinters.NewErrcheck().Name(): true, + golinters.NewErrcheck(errcheckCfg).Name(): true, golinters.NewStaticcheck(staticcheckCfg).Name(): true, golinters.NewUnused(unusedCfg).Name(): true, golinters.NewGosimple(gosimpleCfg).Name(): true, - golinters.NewStructcheck().Name(): true, - golinters.NewVarcheck().Name(): true, + golinters.NewStructcheck(structcheckCfg).Name(): true, + golinters.NewVarcheck(varcheckCfg).Name(): true, golinters.NewIneffassign().Name(): true, golinters.NewDeadcode().Name(): true, golinters.NewTypecheck().Name(): true, diff --git a/pkg/lint/load.go b/pkg/lint/load.go index a393a1d081eb..8935134e1fda 100644 --- a/pkg/lint/load.go +++ b/pkg/lint/load.go @@ -126,7 +126,7 @@ func stringifyLoadMode(mode packages.LoadMode) string { m := map[packages.LoadMode]string{ packages.NeedCompiledGoFiles: "compiled_files", packages.NeedDeps: "deps", - packages.NeedExportsFile: "exports_file", + packages.NeedExportFile: "exports_file", packages.NeedFiles: "files", packages.NeedImports: "imports", packages.NeedName: "name", @@ -192,7 +192,7 @@ func (cl *ContextLoader) loadPackages(ctx context.Context, loadMode packages.Loa Context: ctx, BuildFlags: buildFlags, Logf: cl.debugf, - //TODO: use fset, parsefile, overlay + // TODO: use fset, parsefile, overlay } args := cl.buildArgs() diff --git a/pkg/result/processors/nolint.go b/pkg/result/processors/nolint.go index 12ae0fc2d546..01f597e94d5b 100644 --- a/pkg/result/processors/nolint.go +++ b/pkg/result/processors/nolint.go @@ -33,7 +33,7 @@ func (i *ignoredRange) doesMatch(issue *result.Issue) bool { } // only allow selective nolinting of nolintlint - nolintFoundForLinter := len(i.linters) == 0 && issue.FromLinter != golinters.NolintlintName + nolintFoundForLinter := len(i.linters) == 0 && issue.FromLinter != golinters.NoLintLintName for _, linterName := range i.linters { if linterName == issue.FromLinter { @@ -48,7 +48,7 @@ func (i *ignoredRange) doesMatch(issue *result.Issue) bool { // handle possible unused nolint directives // nolintlint generates potential issues for every nolint directive, and they are filtered out here - if issue.FromLinter == golinters.NolintlintName && issue.ExpectNoLint { + if issue.FromLinter == golinters.NoLintLintName && issue.ExpectNoLint { if issue.ExpectedNoLintLinter != "" { return i.matchedIssueFromLinter[issue.ExpectedNoLintLinter] } @@ -148,7 +148,7 @@ func (p *Nolint) buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, fil func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) { nolintDebugf("got issue: %v", *i) - if i.FromLinter == golinters.NolintlintName && i.ExpectNoLint && i.ExpectedNoLintLinter != "" { + if i.FromLinter == golinters.NoLintLintName && i.ExpectNoLint && i.ExpectedNoLintLinter != "" { // don't expect disabled linters to cover their nolint statements nolintDebugf("enabled linters: %v", p.enabledLinters) if p.enabledLinters[i.ExpectedNoLintLinter] == nil { @@ -302,7 +302,7 @@ func (issues sortWithNolintlintLast) Len() int { } func (issues sortWithNolintlintLast) Less(i, j int) bool { - return issues[i].FromLinter != golinters.NolintlintName && issues[j].FromLinter == golinters.NolintlintName + return issues[i].FromLinter != golinters.NoLintLintName && issues[j].FromLinter == golinters.NoLintLintName } func (issues sortWithNolintlintLast) Swap(i, j int) { diff --git a/pkg/result/processors/nolint_test.go b/pkg/result/processors/nolint_test.go index c54909f2643c..97ab34e51379 100644 --- a/pkg/result/processors/nolint_test.go +++ b/pkg/result/processors/nolint_test.go @@ -286,7 +286,7 @@ func TestNolintUnused(t *testing.T) { Filename: fileName, Line: 3, }, - FromLinter: golinters.NolintlintName, + FromLinter: golinters.NoLintLintName, ExpectNoLint: true, ExpectedNoLintLinter: "varcheck", } @@ -297,7 +297,7 @@ func TestNolintUnused(t *testing.T) { Filename: fileName, Line: 5, }, - FromLinter: golinters.NolintlintName, + FromLinter: golinters.NoLintLintName, ExpectNoLint: true, ExpectedNoLintLinter: "varcheck", } diff --git a/test/testdata/prealloc.go b/test/testdata/prealloc.go index 85f43eb6e297..17900b773e41 100644 --- a/test/testdata/prealloc.go +++ b/test/testdata/prealloc.go @@ -1,8 +1,8 @@ -//args: -Eprealloc +// args: -Eprealloc package testdata func Prealloc(source []int) []int { - var dest []int // ERROR "Consider preallocating `dest`" + var dest []int // ERROR "Consider pre-allocating `dest`" for _, v := range source { dest = append(dest, v) }