mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-28 22:28:43 -04:00
dramatically reduce memory usage (#758)
Run all linters per package. It allows unloading package data when it's processed. It dramatically reduces memory (and CPU because of GC) usage. Relates: #337
This commit is contained in:
parent
fe494af887
commit
95ec0cf21e
74 changed files with 2497 additions and 2439 deletions
1
Makefile
1
Makefile
|
@ -29,6 +29,7 @@ clean:
|
|||
test: export GOLANGCI_LINT_INSTALLED = true
|
||||
test: build
|
||||
GL_TEST_RUN=1 time ./golangci-lint run -v
|
||||
time go run ./cmd/golangci-lint/main.go run -v
|
||||
GL_TEST_RUN=1 time ./golangci-lint run --fast --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
|
||||
GL_TEST_RUN=1 time ./golangci-lint run --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
|
||||
GL_TEST_RUN=1 time go test -v ./...
|
||||
|
|
21
README.md
21
README.md
|
@ -178,13 +178,13 @@ $ golangci-lint help linters
|
|||
Enabled by default linters:
|
||||
deadcode: Finds unused code [fast: true, auto-fix: false]
|
||||
errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
|
||||
gosimple: Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
|
||||
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
|
||||
gosimple (megacheck): Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
||||
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
||||
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||
staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
|
||||
staticcheck (megacheck): Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||
structcheck: Finds unused struct fields [fast: true, auto-fix: false]
|
||||
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
||||
unused: Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
unused (megacheck): Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
varcheck: Finds unused global variables and constants [fast: true, auto-fix: false]
|
||||
```
|
||||
|
||||
|
@ -194,12 +194,12 @@ and the following linters are disabled by default:
|
|||
$ golangci-lint help linters
|
||||
...
|
||||
Disabled by default linters:
|
||||
bodyclose: checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
|
||||
bodyclose: checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||
depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
||||
dogsled: Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
dupl: Tool for code clone detection [fast: true, auto-fix: false]
|
||||
funlen: Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
gochecknoglobals: Checks that no globals are present in Go code [fast: true, auto-fix: false]
|
||||
gochecknoglobals: Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
gochecknoinits: Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
goconst: Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
gocritic: The most opinionated Go source code linter [fast: true, auto-fix: false]
|
||||
|
@ -209,16 +209,16 @@ gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s
|
|||
goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||
golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
|
||||
gosec (gas): Inspects source code for security problems [fast: true, auto-fix: false]
|
||||
interfacer: Linter that suggests narrower interface types [fast: false, auto-fix: false]
|
||||
interfacer: Linter that suggests narrower interface types [fast: true, auto-fix: false]
|
||||
lll: Reports long lines [fast: true, auto-fix: false]
|
||||
maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
|
||||
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
|
||||
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
|
||||
stylecheck: Stylecheck is a replacement for golint [fast: false, auto-fix: false]
|
||||
stylecheck: Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||
unconvert: Remove unnecessary type conversions [fast: true, auto-fix: false]
|
||||
unparam: Reports unused function parameters [fast: false, auto-fix: false]
|
||||
unparam: Reports unused function parameters [fast: true, auto-fix: false]
|
||||
whitespace: Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
|
||||
```
|
||||
|
||||
|
@ -462,7 +462,7 @@ golangci-lint help linters
|
|||
- [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs
|
||||
- [gocritic](https://github.com/go-critic/go-critic) - The most opinionated Go source code linter
|
||||
- [gochecknoinits](https://github.com/leighmcculloch/gochecknoinits) - Checks that no init functions are present in Go code
|
||||
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Checks that no globals are present in Go code
|
||||
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Tool for detection of long functions
|
||||
- [godox](https://github.com/matoous/godox) - Tool for detection of FIXME, TODO and other comment keywords
|
||||
- [funlen](https://github.com/ultraware/funlen) - Tool for detection of long functions
|
||||
- [whitespace](https://github.com/ultraware/whitespace) - Tool for detection of leading and trailing whitespace
|
||||
|
@ -563,6 +563,7 @@ Global Flags:
|
|||
--mem-profile-path string Path to memory profile output file
|
||||
--trace-path string Path to trace output file
|
||||
-v, --verbose verbose output
|
||||
--version Print version
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
if info, available := debug.ReadBuildInfo(); available {
|
||||
if date == "" && info.Main.Version != "(devel)" {
|
||||
if date == "" {
|
||||
version = info.Main.Version
|
||||
commit = fmt.Sprintf("(unknown, mod sum: %q)", info.Main.Sum)
|
||||
date = "(unknown)"
|
||||
|
|
4
go.mod
4
go.mod
|
@ -13,9 +13,9 @@ require (
|
|||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217
|
||||
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21
|
||||
|
|
8
go.sum
8
go.sum
|
@ -86,12 +86,12 @@ github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgO
|
|||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89 h1:664ewjIQUXDvinFMbAsoH2V2Yvaro/X8BoYpIMTWGXI=
|
||||
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
|
||||
|
|
|
@ -265,7 +265,7 @@ func fixSlicesFlags(fs *pflag.FlagSet) {
|
|||
})
|
||||
}
|
||||
|
||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
|
||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
||||
e.cfg.Run.Args = args
|
||||
|
||||
enabledLinters, err := e.EnabledLintersSet.Get(true)
|
||||
|
@ -296,9 +296,9 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
|
|||
return nil, err
|
||||
}
|
||||
|
||||
issuesCh := runner.Run(ctx, enabledLinters, lintCtx)
|
||||
issues := runner.Run(ctx, enabledLinters, lintCtx)
|
||||
fixer := processors.NewFixer(e.cfg, e.log, e.fileCache)
|
||||
return fixer.Process(issuesCh), nil
|
||||
return fixer.Process(issues), nil
|
||||
}
|
||||
|
||||
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
||||
|
@ -313,24 +313,10 @@ func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
|||
return
|
||||
}
|
||||
|
||||
func (e *Executor) setExitCodeIfIssuesFound(issues <-chan result.Issue) <-chan result.Issue {
|
||||
resCh := make(chan result.Issue, 1024)
|
||||
|
||||
go func() {
|
||||
issuesFound := false
|
||||
for i := range issues {
|
||||
issuesFound = true
|
||||
resCh <- i
|
||||
}
|
||||
|
||||
if issuesFound {
|
||||
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
|
||||
}
|
||||
|
||||
close(resCh)
|
||||
}()
|
||||
|
||||
return resCh
|
||||
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
|
||||
if len(issues) != 0 {
|
||||
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
||||
|
@ -357,7 +343,7 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
issues = e.setExitCodeIfIssuesFound(issues)
|
||||
e.setExitCodeIfIssuesFound(issues)
|
||||
|
||||
if err = p.Print(ctx, issues); err != nil {
|
||||
return fmt.Errorf("can't print %d issues: %s", len(issues), err)
|
||||
|
|
|
@ -17,5 +17,5 @@ func NewBodyclose() *goanalysis.Linter {
|
|||
"checks whether HTTP response body is closed successfully",
|
||||
analyzers,
|
||||
nil,
|
||||
)
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,42 +1,52 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
deadcodeAPI "github.com/golangci/go-misc/deadcode"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Deadcode struct{}
|
||||
func NewDeadcode() *goanalysis.Linter {
|
||||
const linterName = "deadcode"
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Deadcode) Name() string {
|
||||
return "deadcode"
|
||||
}
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)),
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
func (Deadcode) Desc() string {
|
||||
return "Finds unused code"
|
||||
}
|
||||
|
||||
func (d Deadcode) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues, err := deadcodeAPI.Run(lintCtx.Program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, lintCtx.Cfg)),
|
||||
FromLinter: d.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
return goanalysis.NewLinter(
|
||||
linterName,
|
||||
"Finds unused code",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
depguardAPI "github.com/OpenPeeDeeP/depguard"
|
||||
|
||||
|
@ -11,12 +16,6 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Depguard struct{}
|
||||
|
||||
func (Depguard) Name() string {
|
||||
return "depguard"
|
||||
}
|
||||
|
||||
func setDepguardListType(dg *depguardAPI.Depguard, lintCtx *linter.Context) error {
|
||||
listType := lintCtx.Settings().Depguard.ListType
|
||||
var found bool
|
||||
|
@ -49,42 +48,67 @@ func setupDepguardPackages(dg *depguardAPI.Depguard, lintCtx *linter.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (Depguard) Desc() string {
|
||||
return "Go linter that checks if package imports are in a list of acceptable packages"
|
||||
}
|
||||
func NewDepguard() *goanalysis.Linter {
|
||||
const linterName = "depguard"
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (d Depguard) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
dg := &depguardAPI.Depguard{
|
||||
Packages: lintCtx.Settings().Depguard.Packages,
|
||||
IncludeGoRoot: lintCtx.Settings().Depguard.IncludeGoRoot,
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
if err := setDepguardListType(dg, lintCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setupDepguardPackages(dg, lintCtx)
|
||||
return goanalysis.NewLinter(
|
||||
linterName,
|
||||
"Go linter that checks if package imports are in a list of acceptable packages",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithContextSetter(func(lintCtx *linter.Context) {
|
||||
dgSettings := &lintCtx.Settings().Depguard
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
prog := goanalysis.MakeFakeLoaderProgram(pass)
|
||||
dg := &depguardAPI.Depguard{
|
||||
Packages: dgSettings.Packages,
|
||||
IncludeGoRoot: dgSettings.IncludeGoRoot,
|
||||
}
|
||||
if err := setDepguardListType(dg, lintCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setupDepguardPackages(dg, lintCtx)
|
||||
|
||||
issues, err := dg.Run(lintCtx.LoaderConfig, lintCtx.Program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
msgSuffix := "is in the blacklist"
|
||||
if dg.ListType == depguardAPI.LTWhitelist {
|
||||
msgSuffix = "is not in the whitelist"
|
||||
}
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
userSuppliedMsgSuffix := lintCtx.Settings().Depguard.PackagesWithErrorMessage[i.PackageName]
|
||||
if userSuppliedMsgSuffix != "" {
|
||||
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
|
||||
loadConfig := &loader.Config{
|
||||
Cwd: "", // fallbacked to os.Getcwd
|
||||
Build: nil, // fallbacked to build.Default
|
||||
}
|
||||
issues, err := dg.Run(loadConfig, prog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
msgSuffix := "is in the blacklist"
|
||||
if dg.ListType == depguardAPI.LTWhitelist {
|
||||
msgSuffix = "is not in the whitelist"
|
||||
}
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
userSuppliedMsgSuffix := dgSettings.PackagesWithErrorMessage[i.PackageName]
|
||||
if userSuppliedMsgSuffix != "" {
|
||||
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
|
||||
}
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Position,
|
||||
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Position,
|
||||
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
|
||||
FromLinter: d.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,37 +1,54 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Dogsled struct{}
|
||||
const dogsledLinterName = "dogsled"
|
||||
|
||||
func (Dogsled) Name() string {
|
||||
return "dogsled"
|
||||
}
|
||||
func NewDogsled() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Dogsled) Desc() string {
|
||||
return "Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())"
|
||||
}
|
||||
|
||||
func (d Dogsled) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
v := returnsVisitor{
|
||||
maxBlanks: lintCtx.Settings().Dogsled.MaxBlankIdentifiers,
|
||||
f: f.Fset,
|
||||
}
|
||||
ast.Walk(&v, f.F)
|
||||
res = append(res, v.issues...)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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 []result.Issue
|
||||
for _, f := range pass.Files {
|
||||
v := returnsVisitor{
|
||||
maxBlanks: lintCtx.Settings().Dogsled.MaxBlankIdentifiers,
|
||||
f: pass.Fset,
|
||||
}
|
||||
ast.Walk(&v, f)
|
||||
pkgIssues = append(pkgIssues, v.issues...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, pkgIssues...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
||||
type returnsVisitor struct {
|
||||
|
@ -68,7 +85,7 @@ func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
if numBlank > v.maxBlanks {
|
||||
v.issues = append(v.issues, result.Issue{
|
||||
FromLinter: Dogsled{}.Name(),
|
||||
FromLinter: dogsledLinterName,
|
||||
Text: fmt.Sprintf("declaration has %v blank identifiers", numBlank),
|
||||
Pos: v.f.Position(assgnStmt.Pos()),
|
||||
})
|
||||
|
|
|
@ -1,60 +1,83 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
duplAPI "github.com/golangci/dupl"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Dupl struct{}
|
||||
const duplLinterName = "dupl"
|
||||
|
||||
func (Dupl) Name() string {
|
||||
return "dupl"
|
||||
}
|
||||
func NewDupl() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Dupl) Desc() string {
|
||||
return "Tool for code clone detection"
|
||||
}
|
||||
|
||||
func (d Dupl) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues, err := duplAPI.Run(getAllFileNames(lintCtx), lintCtx.Settings().Dupl.Threshold)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
issues, err := duplAPI.Run(fileNames, lintCtx.Settings().Dupl.Threshold)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]result.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())
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.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, 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,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
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, 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: d.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -2,13 +2,17 @@ package golinters
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
errcheckAPI "github.com/golangci/errcheck/golangci"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -19,47 +23,58 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Errcheck struct{}
|
||||
|
||||
func (Errcheck) Name() string {
|
||||
return "errcheck"
|
||||
}
|
||||
|
||||
func (Errcheck) Desc() string {
|
||||
return "Errcheck is a program for checking for unchecked errors " +
|
||||
"in go programs. These unchecked errors can be critical bugs in some cases"
|
||||
}
|
||||
|
||||
func (e Errcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
errCfg, err := genConfig(&lintCtx.Settings().Errcheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issues, err := errcheckAPI.RunWithConfig(lintCtx.Program, errCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewErrcheck() *goanalysis.Linter {
|
||||
const linterName = "errcheck"
|
||||
var mu sync.Mutex
|
||||
var res []result.Issue
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
linterName,
|
||||
"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) {
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
prog := goanalysis.MakeFakeLoaderProgram(pass)
|
||||
errCfg, err := genConfig(&lintCtx.Settings().Errcheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
errcheckIssues, err := errcheckAPI.RunWithConfig(prog, errCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(errcheckIssues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
var text string
|
||||
if i.FuncName != "" {
|
||||
text = fmt.Sprintf("Error return value of %s is not checked", formatCode(i.FuncName, lintCtx.Cfg))
|
||||
} else {
|
||||
text = "Error return value is not checked"
|
||||
issues := make([]result.Issue, 0, len(errcheckIssues))
|
||||
for _, i := range errcheckIssues {
|
||||
var text string
|
||||
if i.FuncName != "" {
|
||||
text = fmt.Sprintf("Error return value of %s is not checked", formatCode(i.FuncName, lintCtx.Cfg))
|
||||
} else {
|
||||
text = "Error return value is not checked"
|
||||
}
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: linterName,
|
||||
Text: text,
|
||||
Pos: i.Pos,
|
||||
})
|
||||
}
|
||||
mu.Lock()
|
||||
res = append(res, issues...)
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
res = append(res, result.Issue{
|
||||
FromLinter: e.Name(),
|
||||
Text: text,
|
||||
Pos: i.Pos,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return res
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
||||
// parseIgnoreConfig was taken from errcheck in order to keep the API identical.
|
||||
|
|
|
@ -1,47 +1,65 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/token"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
|
||||
"github.com/ultraware/funlen"
|
||||
)
|
||||
|
||||
type Funlen struct{}
|
||||
const funlenLinterName = "funlen"
|
||||
|
||||
func (Funlen) Name() string {
|
||||
return "funlen"
|
||||
}
|
||||
func NewFunlen() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Funlen) Desc() string {
|
||||
return "Tool for detection of long functions"
|
||||
}
|
||||
|
||||
func (f Funlen) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var issues []funlen.Message
|
||||
for _, file := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
issues = append(issues, funlen.Run(file.F, file.Fset, lintCtx.Settings().Funlen.Lines, lintCtx.Settings().Funlen.Statements)...)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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...)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.Issue, len(issues))
|
||||
for k, i := range issues {
|
||||
res[k] = result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.Pos.Filename,
|
||||
Line: i.Pos.Line,
|
||||
},
|
||||
Text: strings.TrimRight(i.Message, "\n"),
|
||||
FromLinter: f.Name(),
|
||||
res := make([]result.Issue, len(issues))
|
||||
for k, i := range issues {
|
||||
res[k] = result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.Pos.Filename,
|
||||
Line: i.Pos.Line,
|
||||
},
|
||||
Text: strings.TrimRight(i.Message, "\n"),
|
||||
FromLinter: funlenLinterName,
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
36
pkg/golinters/goanalysis/adapters.go
Normal file
36
pkg/golinters/goanalysis/adapters.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package goanalysis
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
func MakeFakeLoaderProgram(pass *analysis.Pass) *loader.Program {
|
||||
prog := &loader.Program{
|
||||
Fset: pass.Fset,
|
||||
Created: []*loader.PackageInfo{
|
||||
{
|
||||
Pkg: pass.Pkg,
|
||||
Importable: true, // not used
|
||||
TransitivelyErrorFree: true, // TODO
|
||||
|
||||
Files: pass.Files,
|
||||
Errors: nil,
|
||||
Info: *pass.TypesInfo,
|
||||
},
|
||||
},
|
||||
AllPackages: map[*types.Package]*loader.PackageInfo{
|
||||
pass.Pkg: {
|
||||
Pkg: pass.Pkg,
|
||||
Importable: true,
|
||||
TransitivelyErrorFree: true,
|
||||
Files: pass.Files,
|
||||
Errors: nil,
|
||||
Info: *pass.TypesInfo,
|
||||
},
|
||||
},
|
||||
}
|
||||
return prog
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package goanalysis
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type SupportedLinter interface {
|
||||
Analyzers() []*analysis.Analyzer
|
||||
Cfg() map[string]map[string]interface{}
|
||||
AnalyzerToLinterNameMapping() map[*analysis.Analyzer]string
|
||||
}
|
|
@ -6,32 +6,95 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
libpackages "github.com/golangci/golangci-lint/pkg/packages"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
const (
|
||||
TheOnlyAnalyzerName = "the_only_name"
|
||||
TheOnlyanalyzerDoc = "the_only_doc"
|
||||
)
|
||||
|
||||
type LoadMode int
|
||||
|
||||
const (
|
||||
LoadModeNone LoadMode = iota
|
||||
LoadModeSyntax
|
||||
LoadModeTypesInfo
|
||||
LoadModeWholeProgram
|
||||
)
|
||||
|
||||
func (loadMode LoadMode) String() string {
|
||||
switch loadMode {
|
||||
case LoadModeNone:
|
||||
return "none"
|
||||
case LoadModeSyntax:
|
||||
return "syntax"
|
||||
case LoadModeTypesInfo:
|
||||
return "types info"
|
||||
case LoadModeWholeProgram:
|
||||
return "whole program"
|
||||
}
|
||||
panic(fmt.Sprintf("unknown load mode %d", loadMode))
|
||||
}
|
||||
|
||||
type Linter struct {
|
||||
name, desc string
|
||||
analyzers []*analysis.Analyzer
|
||||
cfg map[string]map[string]interface{}
|
||||
name, desc string
|
||||
analyzers []*analysis.Analyzer
|
||||
cfg map[string]map[string]interface{}
|
||||
issuesReporter func(*linter.Context) []result.Issue
|
||||
contextSetter func(*linter.Context)
|
||||
loadMode LoadMode
|
||||
useOriginalPackages bool
|
||||
isTypecheckMode bool
|
||||
}
|
||||
|
||||
func NewLinter(name, desc string, analyzers []*analysis.Analyzer, cfg map[string]map[string]interface{}) *Linter {
|
||||
return &Linter{name: name, desc: desc, analyzers: analyzers, cfg: cfg}
|
||||
}
|
||||
|
||||
func (lnt Linter) Name() string {
|
||||
func (lnt *Linter) UseOriginalPackages() {
|
||||
lnt.useOriginalPackages = true
|
||||
}
|
||||
|
||||
func (lnt *Linter) SetTypecheckMode() {
|
||||
lnt.isTypecheckMode = true
|
||||
}
|
||||
|
||||
func (lnt *Linter) LoadMode() LoadMode {
|
||||
return lnt.loadMode
|
||||
}
|
||||
|
||||
func (lnt *Linter) WithLoadMode(loadMode LoadMode) *Linter {
|
||||
lnt.loadMode = loadMode
|
||||
return lnt
|
||||
}
|
||||
|
||||
func (lnt *Linter) WithIssuesReporter(r func(*linter.Context) []result.Issue) *Linter {
|
||||
lnt.issuesReporter = r
|
||||
return lnt
|
||||
}
|
||||
|
||||
func (lnt *Linter) WithContextSetter(cs func(*linter.Context)) *Linter {
|
||||
lnt.contextSetter = cs
|
||||
return lnt
|
||||
}
|
||||
|
||||
func (lnt *Linter) Name() string {
|
||||
return lnt.name
|
||||
}
|
||||
|
||||
func (lnt Linter) Desc() string {
|
||||
func (lnt *Linter) Desc() string {
|
||||
return lnt.desc
|
||||
}
|
||||
|
||||
func (lnt Linter) allAnalyzerNames() []string {
|
||||
func (lnt *Linter) allAnalyzerNames() []string {
|
||||
var ret []string
|
||||
for _, a := range lnt.analyzers {
|
||||
ret = append(ret, a.Name)
|
||||
|
@ -63,7 +126,7 @@ func valueToString(v interface{}) string {
|
|||
return fmt.Sprint(v)
|
||||
}
|
||||
|
||||
func (lnt Linter) configureAnalyzer(a *analysis.Analyzer, cfg map[string]interface{}) error {
|
||||
func (lnt *Linter) configureAnalyzer(a *analysis.Analyzer, cfg map[string]interface{}) error {
|
||||
for k, v := range cfg {
|
||||
f := a.Flags.Lookup(k)
|
||||
if f == nil {
|
||||
|
@ -84,7 +147,7 @@ func (lnt Linter) configureAnalyzer(a *analysis.Analyzer, cfg map[string]interfa
|
|||
return nil
|
||||
}
|
||||
|
||||
func (lnt Linter) configure() error {
|
||||
func (lnt *Linter) configure() error {
|
||||
analyzersMap := map[string]*analysis.Analyzer{}
|
||||
for _, a := range lnt.analyzers {
|
||||
analyzersMap[a.Name] = a
|
||||
|
@ -105,7 +168,63 @@ func (lnt Linter) configure() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (lnt Linter) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
func parseError(srcErr packages.Error) (*result.Issue, error) {
|
||||
pos, err := libpackages.ParseErrorPosition(srcErr.Pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result.Issue{
|
||||
Pos: *pos,
|
||||
Text: srcErr.Msg,
|
||||
FromLinter: "typecheck",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildIssuesFromErrorsForTypecheckMode(errs []error, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var issues []result.Issue
|
||||
uniqReportedIssues := map[string]bool{}
|
||||
for _, err := range errs {
|
||||
itErr, ok := errors.Cause(err).(*IllTypedError)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
for _, err := range libpackages.ExtractErrors(itErr.Pkg, lintCtx.ASTCache) {
|
||||
i, perr := parseError(err)
|
||||
if perr != nil { // failed to parse
|
||||
if uniqReportedIssues[err.Msg] {
|
||||
continue
|
||||
}
|
||||
uniqReportedIssues[err.Msg] = true
|
||||
lintCtx.Log.Errorf("typechecking error: %s", err.Msg)
|
||||
} else {
|
||||
issues = append(issues, *i)
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) string) []result.Issue {
|
||||
var issues []result.Issue
|
||||
for i := range diags {
|
||||
diag := &diags[i]
|
||||
var text string
|
||||
if diag.Analyzer.Name == TheOnlyAnalyzerName {
|
||||
text = diag.Message
|
||||
} else {
|
||||
text = fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message)
|
||||
}
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: linterNameBuilder(diag),
|
||||
Text: text,
|
||||
Pos: diag.Position,
|
||||
})
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
func (lnt *Linter) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
if err := analysis.Validate(lnt.analyzers); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate analyzers")
|
||||
}
|
||||
|
@ -114,39 +233,37 @@ func (lnt Linter) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Is
|
|||
return nil, errors.Wrap(err, "failed to configure analyzers")
|
||||
}
|
||||
|
||||
runner := newRunner(lnt.name, lintCtx.Log.Child("goanalysis"), lintCtx.PkgCache, lintCtx.LoadGuard, lintCtx.NeedWholeProgram)
|
||||
if lnt.contextSetter != nil {
|
||||
lnt.contextSetter(lintCtx)
|
||||
}
|
||||
|
||||
loadMode := lnt.loadMode
|
||||
runner := newRunner(lnt.name, lintCtx.Log.Child("goanalysis"),
|
||||
lintCtx.PkgCache, lintCtx.LoadGuard, loadMode)
|
||||
|
||||
pkgs := lintCtx.Packages
|
||||
if lnt.useOriginalPackages {
|
||||
pkgs = lintCtx.OriginalPackages
|
||||
}
|
||||
|
||||
diags, errs := runner.run(lnt.analyzers, pkgs)
|
||||
|
||||
linterNameBuilder := func(*Diagnostic) string { return lnt.Name() }
|
||||
if lnt.isTypecheckMode {
|
||||
return buildIssuesFromErrorsForTypecheckMode(errs, lintCtx)
|
||||
}
|
||||
|
||||
diags, errs := runner.run(lnt.analyzers, lintCtx.Packages)
|
||||
// Don't print all errs: they can duplicate.
|
||||
if len(errs) != 0 {
|
||||
return nil, errs[0]
|
||||
}
|
||||
|
||||
var issues []result.Issue
|
||||
for i := range diags {
|
||||
diag := &diags[i]
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: lnt.Name(),
|
||||
Text: fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message),
|
||||
Pos: diag.Position,
|
||||
})
|
||||
if lnt.issuesReporter != nil {
|
||||
issues = append(issues, lnt.issuesReporter(lintCtx)...)
|
||||
} else {
|
||||
issues = buildIssues(diags, linterNameBuilder)
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (lnt Linter) Analyzers() []*analysis.Analyzer {
|
||||
return lnt.analyzers
|
||||
}
|
||||
|
||||
func (lnt Linter) Cfg() map[string]map[string]interface{} {
|
||||
return lnt.cfg
|
||||
}
|
||||
|
||||
func (lnt Linter) AnalyzerToLinterNameMapping() map[*analysis.Analyzer]string {
|
||||
ret := map[*analysis.Analyzer]string{}
|
||||
for _, a := range lnt.analyzers {
|
||||
ret[a] = lnt.Name()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package golinters
|
||||
package goanalysis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -19,9 +19,8 @@ func TestParseError(t *testing.T) {
|
|||
{"f.go: 1", "", false},
|
||||
}
|
||||
|
||||
lint := TypeCheck{}
|
||||
for _, c := range cases {
|
||||
i, _ := lint.parseError(packages.Error{
|
||||
i, _ := parseError(packages.Error{
|
||||
Pos: c.in,
|
||||
Msg: "msg",
|
||||
})
|
|
@ -2,7 +2,6 @@ package goanalysis
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
@ -12,12 +11,11 @@ import (
|
|||
)
|
||||
|
||||
type MetaLinter struct {
|
||||
linters []*Linter
|
||||
analyzerToLinterName map[*analysis.Analyzer]string
|
||||
linters []*Linter
|
||||
}
|
||||
|
||||
func NewMetaLinter(linters []*Linter, analyzerToLinterName map[*analysis.Analyzer]string) *MetaLinter {
|
||||
return &MetaLinter{linters: linters, analyzerToLinterName: analyzerToLinterName}
|
||||
func NewMetaLinter(linters []*Linter) *MetaLinter {
|
||||
return &MetaLinter{linters: linters}
|
||||
}
|
||||
|
||||
func (ml MetaLinter) Name() string {
|
||||
|
@ -28,41 +26,110 @@ func (ml MetaLinter) Desc() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (ml MetaLinter) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
func (ml MetaLinter) isTypecheckMode() bool {
|
||||
for _, linter := range ml.linters {
|
||||
if err := analysis.Validate(linter.analyzers); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to validate analyzers of %s", linter.Name())
|
||||
if linter.isTypecheckMode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ml MetaLinter) getLoadMode() LoadMode {
|
||||
loadMode := LoadModeNone
|
||||
for _, linter := range ml.linters {
|
||||
if err := linter.configure(); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to configure analyzers of %s", linter.Name())
|
||||
if linter.loadMode > loadMode {
|
||||
loadMode = linter.loadMode
|
||||
}
|
||||
}
|
||||
return loadMode
|
||||
}
|
||||
|
||||
func (ml MetaLinter) runContextSetters(lintCtx *linter.Context) {
|
||||
for _, linter := range ml.linters {
|
||||
if linter.contextSetter != nil {
|
||||
linter.contextSetter(lintCtx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ml MetaLinter) getAllAnalyzers() []*analysis.Analyzer {
|
||||
var allAnalyzers []*analysis.Analyzer
|
||||
for _, linter := range ml.linters {
|
||||
allAnalyzers = append(allAnalyzers, linter.analyzers...)
|
||||
}
|
||||
return allAnalyzers
|
||||
}
|
||||
|
||||
runner := newRunner("metalinter", lintCtx.Log.Child("goanalysis"), lintCtx.PkgCache, lintCtx.LoadGuard, lintCtx.NeedWholeProgram)
|
||||
func (ml MetaLinter) getAnalyzerToLinterNameMapping() map[*analysis.Analyzer]string {
|
||||
analyzerToLinterName := map[*analysis.Analyzer]string{}
|
||||
for _, linter := range ml.linters {
|
||||
for _, a := range linter.analyzers {
|
||||
analyzerToLinterName[a] = linter.Name()
|
||||
}
|
||||
}
|
||||
return analyzerToLinterName
|
||||
}
|
||||
|
||||
func (ml MetaLinter) configure() error {
|
||||
for _, linter := range ml.linters {
|
||||
if err := linter.configure(); err != nil {
|
||||
return errors.Wrapf(err, "failed to configure analyzers of %s", linter.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml MetaLinter) validate() error {
|
||||
for _, linter := range ml.linters {
|
||||
if err := analysis.Validate(linter.analyzers); err != nil {
|
||||
return errors.Wrapf(err, "failed to validate analyzers of %s", linter.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml MetaLinter) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
if err := ml.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ml.configure(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ml.runContextSetters(lintCtx)
|
||||
|
||||
analyzerToLinterName := ml.getAnalyzerToLinterNameMapping()
|
||||
|
||||
runner := newRunner("metalinter", lintCtx.Log.Child("goanalysis"),
|
||||
lintCtx.PkgCache, lintCtx.LoadGuard, ml.getLoadMode())
|
||||
|
||||
diags, errs := runner.run(ml.getAllAnalyzers(), lintCtx.Packages)
|
||||
|
||||
buildAllIssues := func() []result.Issue {
|
||||
linterNameBuilder := func(diag *Diagnostic) string { return analyzerToLinterName[diag.Analyzer] }
|
||||
issues := buildIssues(diags, linterNameBuilder)
|
||||
|
||||
for _, linter := range ml.linters {
|
||||
if linter.issuesReporter != nil {
|
||||
issues = append(issues, linter.issuesReporter(lintCtx)...)
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
if ml.isTypecheckMode() {
|
||||
issues, err := buildIssuesFromErrorsForTypecheckMode(errs, lintCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(issues, buildAllIssues()...), nil
|
||||
}
|
||||
|
||||
diags, errs := runner.run(allAnalyzers, lintCtx.Packages)
|
||||
// Don't print all errs: they can duplicate.
|
||||
if len(errs) != 0 {
|
||||
return nil, errs[0]
|
||||
}
|
||||
|
||||
var issues []result.Issue
|
||||
for i := range diags {
|
||||
diag := &diags[i]
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: ml.analyzerToLinterName[diag.Analyzer],
|
||||
Text: fmt.Sprintf("%s: %s", diag.Analyzer, diag.Message),
|
||||
Pos: diag.Position,
|
||||
})
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
return buildAllIssues(), nil
|
||||
}
|
||||
|
|
|
@ -54,9 +54,7 @@ var (
|
|||
// v show [v]erbose logging
|
||||
//
|
||||
|
||||
debugf = logutils.Debug("goanalysis")
|
||||
isDebug = logutils.HaveDebugTag("goanalysis")
|
||||
|
||||
debugf = logutils.Debug("goanalysis")
|
||||
factsDebugf = logutils.Debug("goanalysis/facts")
|
||||
factsInheritDebugf = logutils.Debug("goanalysis/facts/inherit")
|
||||
factsExportDebugf = logutils.Debug("goanalysis/facts")
|
||||
|
@ -78,20 +76,20 @@ type Diagnostic struct {
|
|||
}
|
||||
|
||||
type runner struct {
|
||||
log logutils.Log
|
||||
prefix string // ensure unique analyzer names
|
||||
pkgCache *pkgcache.Cache
|
||||
loadGuard *load.Guard
|
||||
needWholeProgram bool
|
||||
log logutils.Log
|
||||
prefix string // ensure unique analyzer names
|
||||
pkgCache *pkgcache.Cache
|
||||
loadGuard *load.Guard
|
||||
loadMode LoadMode
|
||||
}
|
||||
|
||||
func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard, needWholeProgram bool) *runner {
|
||||
func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard, loadMode LoadMode) *runner {
|
||||
return &runner{
|
||||
prefix: prefix,
|
||||
log: logger,
|
||||
pkgCache: pkgCache,
|
||||
loadGuard: loadGuard,
|
||||
needWholeProgram: needWholeProgram,
|
||||
prefix: prefix,
|
||||
log: logger,
|
||||
pkgCache: pkgCache,
|
||||
loadGuard: loadGuard,
|
||||
loadMode: loadMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +101,7 @@ func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loa
|
|||
// It returns the appropriate exit code.
|
||||
//nolint:gocyclo
|
||||
func (r *runner) run(analyzers []*analysis.Analyzer, initialPackages []*packages.Package) ([]Diagnostic, []error) {
|
||||
debugf("Analyzing %d packages on load mode %s", len(initialPackages), r.loadMode)
|
||||
defer r.pkgCache.Trim()
|
||||
|
||||
roots := r.analyze(initialPackages, analyzers)
|
||||
|
@ -144,7 +143,7 @@ func (r *runner) prepareAnalysis(pkgs []*packages.Package,
|
|||
analysisDoneCh: make(chan struct{}),
|
||||
objectFacts: make(map[objectFactKey]analysis.Fact),
|
||||
packageFacts: make(map[packageFactKey]analysis.Fact),
|
||||
needWholeProgram: r.needWholeProgram,
|
||||
loadMode: r.loadMode,
|
||||
}
|
||||
|
||||
// Add a dependency on each required analyzers.
|
||||
|
@ -243,7 +242,7 @@ func (r *runner) analyze(pkgs []*packages.Package, analyzers []*analysis.Analyze
|
|||
if lp.isInitial {
|
||||
wg.Add(1)
|
||||
go func(lp *loadingPackage) {
|
||||
lp.analyzeRecursive(r.needWholeProgram, loadSem)
|
||||
lp.analyzeRecursive(r.loadMode, loadSem)
|
||||
wg.Done()
|
||||
}(lp)
|
||||
}
|
||||
|
@ -344,14 +343,13 @@ type action struct {
|
|||
result interface{}
|
||||
diagnostics []analysis.Diagnostic
|
||||
err error
|
||||
duration time.Duration
|
||||
log logutils.Log
|
||||
prefix string
|
||||
pkgCache *pkgcache.Cache
|
||||
analysisDoneCh chan struct{}
|
||||
loadCachedFactsDone bool
|
||||
loadCachedFactsOk bool
|
||||
needWholeProgram bool
|
||||
loadMode LoadMode
|
||||
}
|
||||
|
||||
type objectFactKey struct {
|
||||
|
@ -397,6 +395,14 @@ func (act *action) waitUntilDependingAnalyzersWorked() {
|
|||
}
|
||||
}
|
||||
|
||||
type IllTypedError struct {
|
||||
Pkg *packages.Package
|
||||
}
|
||||
|
||||
func (e *IllTypedError) Error() string {
|
||||
return fmt.Sprintf("errors in package: %v", e.Pkg.Errors)
|
||||
}
|
||||
|
||||
func (act *action) analyzeSafe() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
|
@ -427,10 +433,7 @@ func (act *action) analyze() {
|
|||
// time is 5x higher than in sequential mode, even with a
|
||||
// semaphore limiting the number of threads here.
|
||||
// So use -debug=tp.
|
||||
if isDebug {
|
||||
t0 := time.Now()
|
||||
defer func() { act.duration = time.Since(t0) }()
|
||||
}
|
||||
|
||||
defer func(now time.Time) {
|
||||
analyzeDebugf("go/analysis: %s: %s: analyzed package %q in %s", act.prefix, act.a.Name, act.pkg.Name, time.Since(now))
|
||||
}(time.Now())
|
||||
|
@ -488,8 +491,11 @@ func (act *action) analyze() {
|
|||
act.pass = pass
|
||||
|
||||
var err error
|
||||
if act.pkg.IllTyped && !pass.Analyzer.RunDespiteErrors {
|
||||
err = fmt.Errorf("analysis skipped due to errors in package")
|
||||
if act.pkg.IllTyped {
|
||||
// It looks like there should be !pass.Analyzer.RunDespiteErrors
|
||||
// but govet's cgocall crashes on it. Govet itself contains !pass.Analyzer.RunDespiteErrors condition here
|
||||
// but it exit before it if packages.Load have failed.
|
||||
err = errors.Wrap(&IllTypedError{Pkg: act.pkg}, "analysis skipped")
|
||||
} else {
|
||||
startedAt = time.Now()
|
||||
act.result, err = pass.Analyzer.Run(pass)
|
||||
|
@ -910,36 +916,36 @@ func (lp *loadingPackage) decUse() {
|
|||
lp.actions = nil
|
||||
}
|
||||
|
||||
func (lp *loadingPackage) analyzeRecursive(needWholeProgram bool, loadSem chan struct{}) {
|
||||
func (lp *loadingPackage) analyzeRecursive(loadMode LoadMode, loadSem chan struct{}) {
|
||||
lp.analyzeOnce.Do(func() {
|
||||
// Load the direct dependencies, in parallel.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(lp.imports))
|
||||
for _, imp := range lp.imports {
|
||||
go func(imp *loadingPackage) {
|
||||
imp.analyzeRecursive(needWholeProgram, loadSem)
|
||||
imp.analyzeRecursive(loadMode, loadSem)
|
||||
wg.Done()
|
||||
}(imp)
|
||||
}
|
||||
wg.Wait()
|
||||
lp.analyze(needWholeProgram, loadSem)
|
||||
lp.analyze(loadMode, loadSem)
|
||||
})
|
||||
}
|
||||
|
||||
func (lp *loadingPackage) analyze(needWholeProgram bool, loadSem chan struct{}) {
|
||||
func (lp *loadingPackage) analyze(loadMode LoadMode, loadSem chan struct{}) {
|
||||
loadSem <- struct{}{}
|
||||
defer func() {
|
||||
<-loadSem
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if !needWholeProgram {
|
||||
if loadMode < LoadModeWholeProgram {
|
||||
// Save memory on unused more fields.
|
||||
lp.decUse()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := lp.loadWithFacts(needWholeProgram); err != nil {
|
||||
if err := lp.loadWithFacts(loadMode); err != nil {
|
||||
werr := errors.Wrapf(err, "failed to load package %s", lp.pkg.Name)
|
||||
// Don't need to write error to errCh, it will be extracted and reported on another layer.
|
||||
// Unblock depending actions and propagate error.
|
||||
|
@ -964,9 +970,32 @@ func (lp *loadingPackage) analyze(needWholeProgram bool, loadSem chan struct{})
|
|||
actsWg.Wait()
|
||||
}
|
||||
|
||||
func (lp *loadingPackage) loadFromSource() error {
|
||||
func (lp *loadingPackage) loadFromSource(loadMode LoadMode) error {
|
||||
pkg := lp.pkg
|
||||
|
||||
// Many packages have few files, much fewer than there
|
||||
// are CPU cores. Additionally, parsing each individual file is
|
||||
// very fast. A naive parallel implementation of this loop won't
|
||||
// be faster, and tends to be slower due to extra scheduling,
|
||||
// bookkeeping and potentially false sharing of cache lines.
|
||||
pkg.Syntax = make([]*ast.File, 0, len(pkg.CompiledGoFiles))
|
||||
for _, file := range pkg.CompiledGoFiles {
|
||||
f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
|
||||
continue
|
||||
}
|
||||
pkg.Syntax = append(pkg.Syntax, f)
|
||||
}
|
||||
if len(pkg.Errors) != 0 {
|
||||
pkg.IllTyped = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if loadMode == LoadModeSyntax {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call NewPackage directly with explicit name.
|
||||
// This avoids skew between golist and go/types when the files'
|
||||
// package declarations are inconsistent.
|
||||
|
@ -979,20 +1008,6 @@ func (lp *loadingPackage) loadFromSource() error {
|
|||
|
||||
pkg.IllTyped = true
|
||||
|
||||
// Many packages have few files, much fewer than there
|
||||
// are CPU cores. Additionally, parsing each individual file is
|
||||
// very fast. A naive parallel implementation of this loop won't
|
||||
// be faster, and tends to be slower due to extra scheduling,
|
||||
// bookkeeping and potentially false sharing of cache lines.
|
||||
pkg.Syntax = make([]*ast.File, len(pkg.CompiledGoFiles))
|
||||
for i, file := range pkg.CompiledGoFiles {
|
||||
f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
|
||||
return err
|
||||
}
|
||||
pkg.Syntax[i] = f
|
||||
}
|
||||
pkg.TypesInfo = &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
|
@ -1027,11 +1042,19 @@ func (lp *loadingPackage) loadFromSource() error {
|
|||
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
|
||||
},
|
||||
}
|
||||
err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
|
||||
if err != nil {
|
||||
return err
|
||||
_ = types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
|
||||
// Don't handle error here: errors are adding by tc.Error function.
|
||||
|
||||
illTyped := len(pkg.Errors) != 0
|
||||
if !illTyped {
|
||||
for _, imp := range lp.imports {
|
||||
if imp.pkg.IllTyped {
|
||||
illTyped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pkg.IllTyped = false
|
||||
pkg.IllTyped = illTyped
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1106,30 +1129,30 @@ func (lp *loadingPackage) loadFromExportData() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (lp *loadingPackage) loadWithFacts(needWholeProgram bool) error {
|
||||
func (act *action) markDepsForAnalyzingSource() {
|
||||
// Horizontal deps (analyzer.Requires) must be loaded from source and analyzed before analyzing
|
||||
// this action.
|
||||
for _, dep := range act.deps {
|
||||
if dep.pkg == act.pkg {
|
||||
// Analyze source only for horizontal dependencies, e.g. from "buildssa".
|
||||
dep.needAnalyzeSource = true // can't be set in parallel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lp *loadingPackage) loadWithFacts(loadMode LoadMode) error {
|
||||
pkg := lp.pkg
|
||||
|
||||
if pkg.PkgPath == unsafePkgName {
|
||||
if !needWholeProgram { // the package wasn't loaded yet
|
||||
// Fill in the blanks to avoid surprises.
|
||||
// Fill in the blanks to avoid surprises.
|
||||
pkg.Syntax = []*ast.File{}
|
||||
if loadMode >= LoadModeTypesInfo {
|
||||
pkg.Types = types.Unsafe
|
||||
pkg.Syntax = []*ast.File{}
|
||||
pkg.TypesInfo = new(types.Info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
markDepsForAnalyzingSource := func(act *action) {
|
||||
// Horizontal deps (analyzer.Requires) must be loaded from source and analyzed before analyzing
|
||||
// this action.
|
||||
for _, dep := range act.deps {
|
||||
if dep.pkg == act.pkg {
|
||||
// Analyze source only for horizontal dependencies, e.g. from "buildssa".
|
||||
dep.needAnalyzeSource = true // can't be set in parallel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pkg.TypesInfo != nil {
|
||||
// Already loaded package, e.g. because another not go/analysis linter required types for deps.
|
||||
// Try load cached facts for it.
|
||||
|
@ -1139,7 +1162,7 @@ func (lp *loadingPackage) loadWithFacts(needWholeProgram bool) error {
|
|||
// Cached facts loading failed: analyze later the action from source.
|
||||
act.needAnalyzeSource = true
|
||||
factsCacheDebugf("Loading of facts for already loaded %s failed, analyze it from source later", act)
|
||||
markDepsForAnalyzingSource(act)
|
||||
act.markDepsForAnalyzingSource()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -1148,32 +1171,40 @@ func (lp *loadingPackage) loadWithFacts(needWholeProgram bool) error {
|
|||
if lp.isInitial {
|
||||
// No need to load cached facts: the package will be analyzed from source
|
||||
// because it's the initial.
|
||||
return lp.loadFromSource()
|
||||
return lp.loadFromSource(loadMode)
|
||||
}
|
||||
|
||||
// Load package from export data
|
||||
if err := lp.loadFromExportData(); err != nil {
|
||||
// We asked Go to give us up to date export data, yet
|
||||
// we can't load it. There must be something wrong.
|
||||
//
|
||||
// Attempt loading from source. This should fail (because
|
||||
// otherwise there would be export data); we just want to
|
||||
// get the compile errors. If loading from source succeeds
|
||||
// we discard the result, anyway. Otherwise we'll fail
|
||||
// when trying to reload from export data later.
|
||||
return lp.loadImportedPackageWithFacts(loadMode)
|
||||
}
|
||||
|
||||
// Otherwise it panics because uses already existing (from exported data) types.
|
||||
pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
|
||||
if srcErr := lp.loadFromSource(); srcErr != nil {
|
||||
return srcErr
|
||||
func (lp *loadingPackage) loadImportedPackageWithFacts(loadMode LoadMode) error {
|
||||
pkg := lp.pkg
|
||||
|
||||
// Load package from export data
|
||||
if loadMode >= LoadModeTypesInfo {
|
||||
if err := lp.loadFromExportData(); err != nil {
|
||||
// We asked Go to give us up to date export data, yet
|
||||
// we can't load it. There must be something wrong.
|
||||
//
|
||||
// Attempt loading from source. This should fail (because
|
||||
// otherwise there would be export data); we just want to
|
||||
// get the compile errors. If loading from source succeeds
|
||||
// we discard the result, anyway. Otherwise we'll fail
|
||||
// when trying to reload from export data later.
|
||||
|
||||
// Otherwise it panics because uses already existing (from exported data) types.
|
||||
pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
|
||||
if srcErr := lp.loadFromSource(loadMode); srcErr != nil {
|
||||
return srcErr
|
||||
}
|
||||
// Make sure this package can't be imported successfully
|
||||
pkg.Errors = append(pkg.Errors, packages.Error{
|
||||
Pos: "-",
|
||||
Msg: fmt.Sprintf("could not load export data: %s", err),
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
return errors.Wrap(err, "could not load export data")
|
||||
}
|
||||
// Make sure this package can't be imported successfully
|
||||
pkg.Errors = append(pkg.Errors, packages.Error{
|
||||
Pos: "-",
|
||||
Msg: fmt.Sprintf("could not load export data: %s", err),
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
return errors.Wrap(err, "could not load export data")
|
||||
}
|
||||
|
||||
needLoadFromSource := false
|
||||
|
@ -1187,7 +1218,7 @@ func (lp *loadingPackage) loadWithFacts(needWholeProgram bool) error {
|
|||
act.needAnalyzeSource = true // can't be set in parallel
|
||||
needLoadFromSource = true
|
||||
|
||||
markDepsForAnalyzingSource(act)
|
||||
act.markDepsForAnalyzingSource()
|
||||
}
|
||||
|
||||
if needLoadFromSource {
|
||||
|
@ -1195,8 +1226,10 @@ func (lp *loadingPackage) loadWithFacts(needWholeProgram bool) error {
|
|||
// the analysis we need to load the package from source code.
|
||||
|
||||
// Otherwise it panics because uses already existing (from exported data) types.
|
||||
pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
|
||||
return lp.loadFromSource()
|
||||
if loadMode >= LoadModeTypesInfo {
|
||||
pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
|
||||
}
|
||||
return lp.loadFromSource(loadMode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,36 +1,57 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gochecknoglobals struct{}
|
||||
const gochecknoglobalsName = "gochecknoglobals"
|
||||
|
||||
func (Gochecknoglobals) Name() string {
|
||||
return "gochecknoglobals"
|
||||
}
|
||||
//nolint:dupl
|
||||
func NewGochecknoglobals() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Gochecknoglobals) Desc() string {
|
||||
return "Checks that no globals are present in Go code"
|
||||
}
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
Run: func(pass *analysis.Pass) (interface{}, error) {
|
||||
var res []result.Issue
|
||||
for _, file := range pass.Files {
|
||||
res = append(res, checkFileForGlobals(file, pass.Fset)...)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (lint Gochecknoglobals) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
res = append(res, lint.checkFile(f.F, f.Fset)...)
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return goanalysis.NewLinter(
|
||||
gochecknoglobalsName,
|
||||
"Tool for detection of long functions",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
||||
func (lint Gochecknoglobals) checkFile(f *ast.File, fset *token.FileSet) []result.Issue {
|
||||
func checkFileForGlobals(f *ast.File, fset *token.FileSet) []result.Issue {
|
||||
var res []result.Issue
|
||||
for _, decl := range f.Decls {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
|
@ -51,7 +72,7 @@ func (lint Gochecknoglobals) checkFile(f *ast.File, fset *token.FileSet) []resul
|
|||
res = append(res, result.Issue{
|
||||
Pos: fset.Position(vn.Pos()),
|
||||
Text: fmt.Sprintf("%s is a global variable", formatCode(vn.Name, nil)),
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: gochecknoglobalsName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,55 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gochecknoinits struct{}
|
||||
const gochecknoinitsName = "gochecknoinits"
|
||||
|
||||
func (Gochecknoinits) Name() string {
|
||||
return "gochecknoinits"
|
||||
}
|
||||
//nolint:dupl
|
||||
func NewGochecknoinits() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Gochecknoinits) Desc() string {
|
||||
return "Checks that no init functions are present in Go code"
|
||||
}
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
Run: func(pass *analysis.Pass) (interface{}, error) {
|
||||
var res []result.Issue
|
||||
for _, file := range pass.Files {
|
||||
res = append(res, checkFileForInits(file, pass.Fset)...)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (lint Gochecknoinits) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
res = append(res, lint.checkFile(f.F, f.Fset)...)
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return goanalysis.NewLinter(
|
||||
gochecknoinitsName,
|
||||
"Checks that no init functions are present in Go code",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
||||
func (lint Gochecknoinits) checkFile(f *ast.File, fset *token.FileSet) []result.Issue {
|
||||
func checkFileForInits(f *ast.File, fset *token.FileSet) []result.Issue {
|
||||
var res []result.Issue
|
||||
for _, decl := range f.Decls {
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
|
@ -42,7 +62,7 @@ func (lint Gochecknoinits) checkFile(f *ast.File, fset *token.FileSet) []result.
|
|||
res = append(res, result.Issue{
|
||||
Pos: fset.Position(funcDecl.Pos()),
|
||||
Text: fmt.Sprintf("don't use %s function", formatCode(name, nil)),
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: gochecknoinitsName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,62 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
goconstAPI "github.com/golangci/goconst"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Goconst struct{}
|
||||
const goconstName = "goconst"
|
||||
|
||||
func (Goconst) Name() string {
|
||||
return "goconst"
|
||||
func NewGoconst() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, issues...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
||||
func (Goconst) Desc() string {
|
||||
return "Finds repeated strings that could be replaced by a constant"
|
||||
}
|
||||
|
||||
func (lint Goconst) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var goconstIssues []goconstAPI.Issue
|
||||
func checkConstants(pass *analysis.Pass, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
cfg := goconstAPI.Config{
|
||||
MatchWithConstants: true,
|
||||
MinStringLength: lintCtx.Settings().Goconst.MinStringLen,
|
||||
MinOccurrences: lintCtx.Settings().Goconst.MinOccurrencesCount,
|
||||
}
|
||||
for _, pkg := range lintCtx.Packages {
|
||||
files, fset, err := getASTFilesForGoPkg(lintCtx, pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issues, err := goconstAPI.Run(files, fset, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goconstIssues = append(goconstIssues, issues...)
|
||||
goconstIssues, err := goconstAPI.Run(pass.Files, pass.Fset, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(goconstIssues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -56,7 +73,7 @@ func (lint Goconst) Run(ctx context.Context, lintCtx *linter.Context) ([]result.
|
|||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: textBegin + textEnd,
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: goconstName,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,70 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gocritic struct{}
|
||||
const gocriticName = "gocritic"
|
||||
|
||||
func (Gocritic) Name() string {
|
||||
return "gocritic"
|
||||
func NewGocritic() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
sizes := types.SizesFor("gc", runtime.GOARCH)
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
gocriticName,
|
||||
"The most opinionated Go source code linter",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithContextSetter(func(lintCtx *linter.Context) {
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
lintpackCtx := lintpack.NewContext(pass.Fset, sizes)
|
||||
enabledCheckers, err := buildEnabledCheckers(lintCtx, lintpackCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lintpackCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg)
|
||||
res := runGocriticOnPackage(lintpackCtx, enabledCheckers, pass.Files)
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
||||
func (Gocritic) Desc() string {
|
||||
return "The most opinionated Go source code linter"
|
||||
}
|
||||
|
||||
func (Gocritic) normalizeCheckerInfoParams(info *lintpack.CheckerInfo) lintpack.CheckerParams {
|
||||
func normalizeCheckerInfoParams(info *lintpack.CheckerInfo) lintpack.CheckerParams {
|
||||
// lowercase info param keys here because golangci-lint's config parser lowercases all strings
|
||||
ret := lintpack.CheckerParams{}
|
||||
for k, v := range info.Params {
|
||||
|
@ -41,13 +74,13 @@ func (Gocritic) normalizeCheckerInfoParams(info *lintpack.CheckerInfo) lintpack.
|
|||
return ret
|
||||
}
|
||||
|
||||
func (lint Gocritic) configureCheckerInfo(info *lintpack.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error {
|
||||
func configureCheckerInfo(info *lintpack.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error {
|
||||
params := allParams[strings.ToLower(info.Name)]
|
||||
if params == nil { // no config for this checker
|
||||
return nil
|
||||
}
|
||||
|
||||
infoParams := lint.normalizeCheckerInfoParams(info)
|
||||
infoParams := normalizeCheckerInfoParams(info)
|
||||
for k, p := range params {
|
||||
v, ok := infoParams[k]
|
||||
if ok {
|
||||
|
@ -74,7 +107,7 @@ func (lint Gocritic) configureCheckerInfo(info *lintpack.CheckerInfo, allParams
|
|||
return nil
|
||||
}
|
||||
|
||||
func (lint Gocritic) buildEnabledCheckers(lintCtx *linter.Context, lintpackCtx *lintpack.Context) ([]*lintpack.Checker, error) {
|
||||
func buildEnabledCheckers(lintCtx *linter.Context, lintpackCtx *lintpack.Context) ([]*lintpack.Checker, error) {
|
||||
s := lintCtx.Settings().Gocritic
|
||||
allParams := s.GetLowercasedParams()
|
||||
|
||||
|
@ -84,7 +117,7 @@ func (lint Gocritic) buildEnabledCheckers(lintCtx *linter.Context, lintpackCtx *
|
|||
continue
|
||||
}
|
||||
|
||||
if err := lint.configureCheckerInfo(info, allParams); err != nil {
|
||||
if err := configureCheckerInfo(info, allParams); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -95,73 +128,34 @@ func (lint Gocritic) buildEnabledCheckers(lintCtx *linter.Context, lintpackCtx *
|
|||
return enabledCheckers, nil
|
||||
}
|
||||
|
||||
func (lint Gocritic) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
sizes := types.SizesFor("gc", runtime.GOARCH)
|
||||
lintpackCtx := lintpack.NewContext(lintCtx.Program.Fset, sizes)
|
||||
|
||||
enabledCheckers, err := lint.buildEnabledCheckers(lintCtx, lintpackCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issuesCh := make(chan result.Issue, 1024)
|
||||
var panicErr error
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
panicErr = fmt.Errorf("panic occurred: %s", err)
|
||||
lintCtx.Log.Warnf("Panic: %s", debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
for _, pkgInfo := range lintCtx.Program.InitialPackages() {
|
||||
lintpackCtx.SetPackageInfo(&pkgInfo.Info, pkgInfo.Pkg)
|
||||
lint.runOnPackage(lintpackCtx, enabledCheckers, pkgInfo, issuesCh)
|
||||
}
|
||||
close(issuesCh)
|
||||
}()
|
||||
|
||||
func runGocriticOnPackage(lintpackCtx *lintpack.Context, checkers []*lintpack.Checker,
|
||||
files []*ast.File) []result.Issue {
|
||||
var res []result.Issue
|
||||
for i := range issuesCh {
|
||||
res = append(res, i)
|
||||
}
|
||||
if panicErr != nil {
|
||||
return nil, panicErr
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (lint Gocritic) runOnPackage(lintpackCtx *lintpack.Context, checkers []*lintpack.Checker,
|
||||
pkgInfo *loader.PackageInfo, ret chan<- result.Issue) {
|
||||
for _, f := range pkgInfo.Files {
|
||||
for _, f := range files {
|
||||
filename := filepath.Base(lintpackCtx.FileSet.Position(f.Pos()).Filename)
|
||||
lintpackCtx.SetFileInfo(filename, f)
|
||||
|
||||
lint.runOnFile(lintpackCtx, f, checkers, ret)
|
||||
issues := runGocriticOnFile(lintpackCtx, f, checkers)
|
||||
res = append(res, issues...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (lint Gocritic) runOnFile(ctx *lintpack.Context, f *ast.File, checkers []*lintpack.Checker,
|
||||
ret chan<- result.Issue) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(checkers))
|
||||
func runGocriticOnFile(ctx *lintpack.Context, f *ast.File, checkers []*lintpack.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.
|
||||
go func(c *lintpack.Checker) {
|
||||
defer wg.Done()
|
||||
|
||||
for _, warn := range c.Check(f) {
|
||||
pos := ctx.FileSet.Position(warn.Node.Pos())
|
||||
ret <- result.Issue{
|
||||
Pos: pos,
|
||||
Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text),
|
||||
FromLinter: lint.Name(),
|
||||
}
|
||||
}
|
||||
}(c)
|
||||
for _, warn := range c.Check(f) {
|
||||
pos := ctx.FileSet.Position(warn.Node.Pos())
|
||||
res = append(res, result.Issue{
|
||||
Pos: pos,
|
||||
Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text),
|
||||
FromLinter: gocriticName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -1,52 +1,68 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
gocycloAPI "github.com/golangci/gocyclo/pkg/gocyclo"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gocyclo struct{}
|
||||
const gocycloName = "gocyclo"
|
||||
|
||||
func (Gocyclo) Name() string {
|
||||
return "gocyclo"
|
||||
}
|
||||
func NewGocyclo() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Gocyclo) Desc() string {
|
||||
return "Computes and checks the cyclomatic complexity of functions"
|
||||
}
|
||||
|
||||
func (g Gocyclo) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var stats []gocycloAPI.Stat
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
stats = gocycloAPI.BuildStats(f.F, f.Fset, stats)
|
||||
}
|
||||
if len(stats) == 0 {
|
||||
return nil, nil
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
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 []gocycloAPI.Stat
|
||||
for _, f := range pass.Files {
|
||||
stats = gocycloAPI.BuildStats(f, pass.Fset, stats)
|
||||
}
|
||||
if len(stats) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sort.Slice(stats, func(i, j int) bool {
|
||||
return stats[i].Complexity > stats[j].Complexity
|
||||
})
|
||||
sort.Slice(stats, func(i, j int) bool {
|
||||
return stats[i].Complexity > stats[j].Complexity
|
||||
})
|
||||
|
||||
res := make([]result.Issue, 0, len(stats))
|
||||
for _, s := range stats {
|
||||
if s.Complexity <= lintCtx.Settings().Gocyclo.MinComplexity {
|
||||
break // Break as the stats is already sorted from greatest to least
|
||||
res := make([]result.Issue, 0, len(stats))
|
||||
for _, s := range stats {
|
||||
if s.Complexity <= lintCtx.Settings().Gocyclo.MinComplexity {
|
||||
break // Break as the stats is already sorted from greatest to least
|
||||
}
|
||||
|
||||
res = append(res, 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,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res = append(res, 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: g.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,46 +1,64 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
"sync"
|
||||
|
||||
"github.com/matoous/godox"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Godox struct{}
|
||||
const godoxName = "godox"
|
||||
|
||||
func (Godox) Name() string {
|
||||
return "godox"
|
||||
}
|
||||
func NewGodox() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Godox) Desc() string {
|
||||
return "Tool for detection of FIXME, TODO and other comment keywords"
|
||||
}
|
||||
|
||||
func (f Godox) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var issues []godox.Message
|
||||
for _, file := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
issues = append(issues, godox.Run(file.F, file.Fset, lintCtx.Settings().Godox.Keywords...)...)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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...)...)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.Issue, len(issues))
|
||||
for k, i := range issues {
|
||||
res[k] = result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.Pos.Filename,
|
||||
Line: i.Pos.Line,
|
||||
},
|
||||
Text: strings.TrimRight(i.Message, "\n"),
|
||||
FromLinter: f.Name(),
|
||||
res := make([]result.Issue, len(issues))
|
||||
for k, i := range issues {
|
||||
res[k] = result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.Pos.Filename,
|
||||
Line: i.Pos.Line,
|
||||
},
|
||||
Text: strings.TrimRight(i.Message, "\n"),
|
||||
FromLinter: godoxName,
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,318 +1,71 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
gofmtAPI "github.com/golangci/gofmt/gofmt"
|
||||
goimportsAPI "github.com/golangci/gofmt/goimports"
|
||||
"github.com/pkg/errors"
|
||||
diffpkg "github.com/sourcegraph/go-diff/diff"
|
||||
"golang.org/x/tools/imports"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gofmt struct {
|
||||
UseGoimports bool
|
||||
}
|
||||
const gofmtName = "gofmt"
|
||||
|
||||
func (g Gofmt) Name() string {
|
||||
if g.UseGoimports {
|
||||
return "goimports"
|
||||
func NewGofmt() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
|
||||
return "gofmt"
|
||||
}
|
||||
|
||||
func (g Gofmt) Desc() string {
|
||||
if g.UseGoimports {
|
||||
return "Goimports does everything that gofmt does. Additionally it checks unused imports"
|
||||
}
|
||||
|
||||
return "Gofmt checks whether code was gofmt-ed. By default " +
|
||||
"this tool runs with -s option to check for code simplification"
|
||||
}
|
||||
|
||||
type Change struct {
|
||||
LineRange result.Range
|
||||
Replacement result.Replacement
|
||||
}
|
||||
|
||||
type diffLineType string
|
||||
|
||||
const (
|
||||
diffLineAdded diffLineType = "added"
|
||||
diffLineOriginal diffLineType = "original"
|
||||
diffLineDeleted diffLineType = "deleted"
|
||||
)
|
||||
|
||||
type diffLine struct {
|
||||
originalNumber int // 1-based original line number
|
||||
typ diffLineType
|
||||
data string // "+" or "-" stripped line
|
||||
}
|
||||
|
||||
type hunkChangesParser struct {
|
||||
// needed because we merge currently added lines with the last original line
|
||||
lastOriginalLine *diffLine
|
||||
|
||||
// if the first line of diff is an adding we save all additions to replacementLinesToPrepend
|
||||
replacementLinesToPrepend []string
|
||||
|
||||
log logutils.Log
|
||||
|
||||
lines []diffLine
|
||||
|
||||
ret []Change
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
|
||||
lines := bytes.Split(h.Body, []byte{'\n'})
|
||||
currentOriginalLineNumer := int(h.OrigStartLine)
|
||||
var ret []diffLine
|
||||
|
||||
for i, line := range lines {
|
||||
dl := diffLine{
|
||||
originalNumber: currentOriginalLineNumer,
|
||||
}
|
||||
|
||||
lineStr := string(line)
|
||||
|
||||
//nolint:gocritic
|
||||
if strings.HasPrefix(lineStr, "-") {
|
||||
dl.typ = diffLineDeleted
|
||||
dl.data = strings.TrimPrefix(lineStr, "-")
|
||||
currentOriginalLineNumer++
|
||||
} else if strings.HasPrefix(lineStr, "+") {
|
||||
dl.typ = diffLineAdded
|
||||
dl.data = strings.TrimPrefix(lineStr, "+")
|
||||
} else {
|
||||
if i == len(lines)-1 && lineStr == "" {
|
||||
// handle last \n: don't add an empty original line
|
||||
break
|
||||
return goanalysis.NewLinter(
|
||||
gofmtName,
|
||||
"Gofmt checks whether code was gofmt-ed. By default "+
|
||||
"this tool runs with -s option to check for code simplification",
|
||||
[]*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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
dl.typ = diffLineOriginal
|
||||
dl.data = strings.TrimPrefix(lineStr, " ")
|
||||
currentOriginalLineNumer++
|
||||
}
|
||||
var issues []result.Issue
|
||||
|
||||
ret = append(ret, dl)
|
||||
}
|
||||
|
||||
p.lines = ret
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
|
||||
if len(p.replacementLinesToPrepend) == 0 {
|
||||
p.lastOriginalLine = &line
|
||||
*i++
|
||||
return
|
||||
}
|
||||
|
||||
// check following added lines for the case:
|
||||
// + added line 1
|
||||
// original line
|
||||
// + added line 2
|
||||
|
||||
*i++
|
||||
var followingAddedLines []string
|
||||
for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
|
||||
followingAddedLines = append(followingAddedLines, p.lines[*i].data)
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, Change{
|
||||
LineRange: result.Range{
|
||||
From: line.originalNumber,
|
||||
To: line.originalNumber,
|
||||
},
|
||||
Replacement: result.Replacement{
|
||||
NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
|
||||
},
|
||||
})
|
||||
p.replacementLinesToPrepend = nil
|
||||
p.lastOriginalLine = &line
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
|
||||
change := Change{
|
||||
LineRange: result.Range{
|
||||
From: deletedLines[0].originalNumber,
|
||||
To: deletedLines[len(deletedLines)-1].originalNumber,
|
||||
},
|
||||
}
|
||||
|
||||
if len(addedLines) != 0 {
|
||||
//nolint:gocritic
|
||||
change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...)
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
p.replacementLinesToPrepend = nil
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, change)
|
||||
return
|
||||
}
|
||||
|
||||
// delete-only change with possible prepending
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
change.Replacement.NewLines = p.replacementLinesToPrepend
|
||||
p.replacementLinesToPrepend = nil
|
||||
} else {
|
||||
change.Replacement.NeedOnlyDelete = true
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, change)
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
|
||||
if p.lastOriginalLine == nil {
|
||||
// the first line is added; the diff looks like:
|
||||
// 1. + ...
|
||||
// 2. - ...
|
||||
// or
|
||||
// 1. + ...
|
||||
// 2. ...
|
||||
|
||||
p.replacementLinesToPrepend = addedLines
|
||||
return
|
||||
}
|
||||
|
||||
// add-only change merged into the last original line with possible prepending
|
||||
p.ret = append(p.ret, Change{
|
||||
LineRange: result.Range{
|
||||
From: p.lastOriginalLine.originalNumber,
|
||||
To: p.lastOriginalLine.originalNumber,
|
||||
},
|
||||
Replacement: result.Replacement{
|
||||
NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
|
||||
},
|
||||
})
|
||||
p.replacementLinesToPrepend = nil
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
|
||||
p.parseDiffLines(h)
|
||||
|
||||
for i := 0; i < len(p.lines); {
|
||||
line := p.lines[i]
|
||||
if line.typ == diffLineOriginal {
|
||||
p.handleOriginalLine(line, &i) //nolint:scopelint
|
||||
continue
|
||||
}
|
||||
|
||||
var deletedLines []diffLine
|
||||
for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
|
||||
deletedLines = append(deletedLines, p.lines[i])
|
||||
}
|
||||
|
||||
var addedLines []string
|
||||
for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
|
||||
addedLines = append(addedLines, p.lines[i].data)
|
||||
}
|
||||
|
||||
if len(deletedLines) != 0 {
|
||||
p.handleDeletedLines(deletedLines, addedLines)
|
||||
continue
|
||||
}
|
||||
|
||||
// no deletions, only addings
|
||||
p.handleAddedOnlyLines(addedLines)
|
||||
}
|
||||
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.ret
|
||||
}
|
||||
|
||||
func (g Gofmt) extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't parse patch")
|
||||
}
|
||||
|
||||
if len(diffs) == 0 {
|
||||
return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs)
|
||||
}
|
||||
|
||||
issues := []result.Issue{}
|
||||
for _, d := range diffs {
|
||||
if len(d.Hunks) == 0 {
|
||||
log.Warnf("Got no hunks in diff %+v", d)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, hunk := range d.Hunks {
|
||||
var text string
|
||||
if g.UseGoimports {
|
||||
text = "File is not `goimports`-ed"
|
||||
} else {
|
||||
text = "File is not `gofmt`-ed"
|
||||
if lintCtx.Settings().Gofmt.Simplify {
|
||||
text += " with `-s`"
|
||||
for _, f := range fileNames {
|
||||
diff, err := gofmtAPI.Run(f, lintCtx.Settings().Gofmt.Simplify)
|
||||
if err != nil { // TODO: skip
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
p := hunkChangesParser{
|
||||
log: log,
|
||||
}
|
||||
changes := p.parse(hunk)
|
||||
for _, change := range changes {
|
||||
change := change // fix scope
|
||||
i := result.Issue{
|
||||
FromLinter: g.Name(),
|
||||
Pos: token.Position{
|
||||
Filename: d.NewName,
|
||||
Line: change.LineRange.From,
|
||||
},
|
||||
Text: text,
|
||||
Replacement: &change.Replacement,
|
||||
}
|
||||
if change.LineRange.From != change.LineRange.To {
|
||||
i.LineRange = &change.LineRange
|
||||
if diff == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
issues = append(issues, i)
|
||||
is, err := extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff))
|
||||
}
|
||||
|
||||
issues = append(issues, is...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (g Gofmt) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var issues []result.Issue
|
||||
|
||||
for _, f := range getAllFileNames(lintCtx) {
|
||||
var diff []byte
|
||||
var err error
|
||||
if g.UseGoimports {
|
||||
imports.LocalPrefix = lintCtx.Settings().Goimports.LocalPrefixes
|
||||
diff, err = goimportsAPI.Run(f)
|
||||
} else {
|
||||
diff, err = gofmtAPI.Run(f, lintCtx.Settings().Gofmt.Simplify)
|
||||
}
|
||||
if err != nil { // TODO: skip
|
||||
return nil, err
|
||||
}
|
||||
if diff == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
is, err := g.extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err)
|
||||
}
|
||||
|
||||
issues = append(issues, is...)
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, issues...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
267
pkg/golinters/gofmt_common.go
Normal file
267
pkg/golinters/gofmt_common.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
diffpkg "github.com/sourcegraph/go-diff/diff"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
LineRange result.Range
|
||||
Replacement result.Replacement
|
||||
}
|
||||
|
||||
type diffLineType string
|
||||
|
||||
const (
|
||||
diffLineAdded diffLineType = "added"
|
||||
diffLineOriginal diffLineType = "original"
|
||||
diffLineDeleted diffLineType = "deleted"
|
||||
)
|
||||
|
||||
type diffLine struct {
|
||||
originalNumber int // 1-based original line number
|
||||
typ diffLineType
|
||||
data string // "+" or "-" stripped line
|
||||
}
|
||||
|
||||
type hunkChangesParser struct {
|
||||
// needed because we merge currently added lines with the last original line
|
||||
lastOriginalLine *diffLine
|
||||
|
||||
// if the first line of diff is an adding we save all additions to replacementLinesToPrepend
|
||||
replacementLinesToPrepend []string
|
||||
|
||||
log logutils.Log
|
||||
|
||||
lines []diffLine
|
||||
|
||||
ret []Change
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
|
||||
lines := bytes.Split(h.Body, []byte{'\n'})
|
||||
currentOriginalLineNumer := int(h.OrigStartLine)
|
||||
var ret []diffLine
|
||||
|
||||
for i, line := range lines {
|
||||
dl := diffLine{
|
||||
originalNumber: currentOriginalLineNumer,
|
||||
}
|
||||
|
||||
lineStr := string(line)
|
||||
|
||||
//nolint:gocritic
|
||||
if strings.HasPrefix(lineStr, "-") {
|
||||
dl.typ = diffLineDeleted
|
||||
dl.data = strings.TrimPrefix(lineStr, "-")
|
||||
currentOriginalLineNumer++
|
||||
} else if strings.HasPrefix(lineStr, "+") {
|
||||
dl.typ = diffLineAdded
|
||||
dl.data = strings.TrimPrefix(lineStr, "+")
|
||||
} else {
|
||||
if i == len(lines)-1 && lineStr == "" {
|
||||
// handle last \n: don't add an empty original line
|
||||
break
|
||||
}
|
||||
|
||||
dl.typ = diffLineOriginal
|
||||
dl.data = strings.TrimPrefix(lineStr, " ")
|
||||
currentOriginalLineNumer++
|
||||
}
|
||||
|
||||
ret = append(ret, dl)
|
||||
}
|
||||
|
||||
p.lines = ret
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
|
||||
if len(p.replacementLinesToPrepend) == 0 {
|
||||
p.lastOriginalLine = &line
|
||||
*i++
|
||||
return
|
||||
}
|
||||
|
||||
// check following added lines for the case:
|
||||
// + added line 1
|
||||
// original line
|
||||
// + added line 2
|
||||
|
||||
*i++
|
||||
var followingAddedLines []string
|
||||
for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
|
||||
followingAddedLines = append(followingAddedLines, p.lines[*i].data)
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, Change{
|
||||
LineRange: result.Range{
|
||||
From: line.originalNumber,
|
||||
To: line.originalNumber,
|
||||
},
|
||||
Replacement: result.Replacement{
|
||||
NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
|
||||
},
|
||||
})
|
||||
p.replacementLinesToPrepend = nil
|
||||
p.lastOriginalLine = &line
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
|
||||
change := Change{
|
||||
LineRange: result.Range{
|
||||
From: deletedLines[0].originalNumber,
|
||||
To: deletedLines[len(deletedLines)-1].originalNumber,
|
||||
},
|
||||
}
|
||||
|
||||
if len(addedLines) != 0 {
|
||||
//nolint:gocritic
|
||||
change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...)
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
p.replacementLinesToPrepend = nil
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, change)
|
||||
return
|
||||
}
|
||||
|
||||
// delete-only change with possible prepending
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
change.Replacement.NewLines = p.replacementLinesToPrepend
|
||||
p.replacementLinesToPrepend = nil
|
||||
} else {
|
||||
change.Replacement.NeedOnlyDelete = true
|
||||
}
|
||||
|
||||
p.ret = append(p.ret, change)
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
|
||||
if p.lastOriginalLine == nil {
|
||||
// the first line is added; the diff looks like:
|
||||
// 1. + ...
|
||||
// 2. - ...
|
||||
// or
|
||||
// 1. + ...
|
||||
// 2. ...
|
||||
|
||||
p.replacementLinesToPrepend = addedLines
|
||||
return
|
||||
}
|
||||
|
||||
// add-only change merged into the last original line with possible prepending
|
||||
p.ret = append(p.ret, Change{
|
||||
LineRange: result.Range{
|
||||
From: p.lastOriginalLine.originalNumber,
|
||||
To: p.lastOriginalLine.originalNumber,
|
||||
},
|
||||
Replacement: result.Replacement{
|
||||
NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
|
||||
},
|
||||
})
|
||||
p.replacementLinesToPrepend = nil
|
||||
}
|
||||
|
||||
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
|
||||
p.parseDiffLines(h)
|
||||
|
||||
for i := 0; i < len(p.lines); {
|
||||
line := p.lines[i]
|
||||
if line.typ == diffLineOriginal {
|
||||
p.handleOriginalLine(line, &i) //nolint:scopelint
|
||||
continue
|
||||
}
|
||||
|
||||
var deletedLines []diffLine
|
||||
for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
|
||||
deletedLines = append(deletedLines, p.lines[i])
|
||||
}
|
||||
|
||||
var addedLines []string
|
||||
for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
|
||||
addedLines = append(addedLines, p.lines[i].data)
|
||||
}
|
||||
|
||||
if len(deletedLines) != 0 {
|
||||
p.handleDeletedLines(deletedLines, addedLines)
|
||||
continue
|
||||
}
|
||||
|
||||
// no deletions, only addings
|
||||
p.handleAddedOnlyLines(addedLines)
|
||||
}
|
||||
|
||||
if len(p.replacementLinesToPrepend) != 0 {
|
||||
p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.ret
|
||||
}
|
||||
|
||||
func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, isGoimports bool) ([]result.Issue, error) {
|
||||
diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't parse patch")
|
||||
}
|
||||
|
||||
if len(diffs) == 0 {
|
||||
return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs)
|
||||
}
|
||||
|
||||
issues := []result.Issue{}
|
||||
for _, d := range diffs {
|
||||
if len(d.Hunks) == 0 {
|
||||
log.Warnf("Got no hunks in diff %+v", d)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, hunk := range d.Hunks {
|
||||
var text string
|
||||
if isGoimports {
|
||||
text = "File is not `goimports`-ed"
|
||||
} else {
|
||||
text = "File is not `gofmt`-ed"
|
||||
if lintCtx.Settings().Gofmt.Simplify {
|
||||
text += " with `-s`"
|
||||
}
|
||||
}
|
||||
p := hunkChangesParser{
|
||||
log: log,
|
||||
}
|
||||
changes := p.parse(hunk)
|
||||
for _, change := range changes {
|
||||
change := change // fix scope
|
||||
linterName := gofmtName
|
||||
if isGoimports {
|
||||
linterName = goimportsName
|
||||
}
|
||||
i := result.Issue{
|
||||
FromLinter: linterName,
|
||||
Pos: token.Position{
|
||||
Filename: d.NewName,
|
||||
Line: change.LineRange.From,
|
||||
},
|
||||
Text: text,
|
||||
Replacement: &change.Replacement,
|
||||
}
|
||||
if change.LineRange.From != change.LineRange.To {
|
||||
i.LineRange = &change.LineRange
|
||||
}
|
||||
|
||||
issues = append(issues, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
72
pkg/golinters/goimports.go
Normal file
72
pkg/golinters/goimports.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
goimportsAPI "github.com/golangci/gofmt/goimports"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/imports"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
const goimportsName = "goimports"
|
||||
|
||||
func NewGoimports() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
goimportsName,
|
||||
"Goimports does everything that gofmt does. Additionally it checks unused imports",
|
||||
[]*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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
var issues []result.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, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff))
|
||||
}
|
||||
|
||||
issues = append(issues, is...)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, issues...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
|
@ -1,51 +1,20 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
lintAPI "github.com/golangci/lint-1"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Golint struct{}
|
||||
|
||||
func (Golint) Name() string {
|
||||
return "golint"
|
||||
}
|
||||
|
||||
func (Golint) Desc() string {
|
||||
return "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"
|
||||
}
|
||||
|
||||
func (g Golint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var issues []result.Issue
|
||||
var lintErr error
|
||||
for _, pkg := range lintCtx.Packages {
|
||||
files, fset, err := getASTFilesForGoPkg(lintCtx, pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i, err := g.lintPkg(lintCtx.Settings().Golint.MinConfidence, files, fset)
|
||||
if err != nil {
|
||||
lintErr = err
|
||||
continue
|
||||
}
|
||||
issues = append(issues, i...)
|
||||
}
|
||||
if lintErr != nil {
|
||||
lintCtx.Log.Warnf("Golint: %s", lintErr)
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (g Golint) lintPkg(minConfidence float64, files []*ast.File, fset *token.FileSet) ([]result.Issue, error) {
|
||||
func golintProcessPkg(minConfidence float64, files []*ast.File, fset *token.FileSet) ([]result.Issue, error) {
|
||||
l := new(lintAPI.Linter)
|
||||
ps, err := l.LintASTFiles(files, fset)
|
||||
if err != nil {
|
||||
|
@ -62,7 +31,7 @@ func (g Golint) lintPkg(minConfidence float64, files []*ast.File, fset *token.Fi
|
|||
issues = append(issues, result.Issue{
|
||||
Pos: ps[idx].Position,
|
||||
Text: ps[idx].Text,
|
||||
FromLinter: g.Name(),
|
||||
FromLinter: golintName,
|
||||
})
|
||||
// TODO: use p.Link and p.Category
|
||||
}
|
||||
|
@ -70,3 +39,36 @@ func (g Golint) lintPkg(minConfidence float64, files []*ast.File, fset *token.Fi
|
|||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
const golintName = "golint"
|
||||
|
||||
func NewGolint() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
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)
|
||||
if err != nil || len(res) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
"github.com/securego/gosec"
|
||||
"github.com/securego/gosec/rules"
|
||||
|
@ -15,55 +20,73 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Gosec struct{}
|
||||
const gosecName = "gosec"
|
||||
|
||||
func (Gosec) Name() string {
|
||||
return "gosec"
|
||||
}
|
||||
func NewGosec() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Gosec) Desc() string {
|
||||
return "Inspects source code for security problems"
|
||||
}
|
||||
|
||||
func (lint Gosec) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
gasConfig := gosec.NewConfig()
|
||||
enabledRules := rules.Generate()
|
||||
logger := log.New(ioutil.Discard, "", 0)
|
||||
analyzer := gosec.NewAnalyzer(gasConfig, true, logger)
|
||||
analyzer.LoadRules(enabledRules.Builders())
|
||||
|
||||
for _, pkg := range lintCtx.Packages {
|
||||
analyzer.Check(pkg)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
issues, _, _ := analyzer.Report()
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.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
|
||||
return goanalysis.NewLinter(
|
||||
gosecName,
|
||||
"Inspects source code for security problems",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithContextSetter(func(lintCtx *linter.Context) {
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
gosecAnalyzer := gosec.NewAnalyzer(gasConfig, true, logger)
|
||||
gosecAnalyzer.LoadRules(enabledRules.Builders())
|
||||
pkg := &packages.Package{
|
||||
Fset: pass.Fset,
|
||||
Syntax: pass.Files,
|
||||
Types: pass.Pkg,
|
||||
TypesInfo: pass.TypesInfo,
|
||||
}
|
||||
line = r.From
|
||||
gosecAnalyzer.Check(pkg)
|
||||
issues, _, _ := gosecAnalyzer.Report()
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.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
|
||||
}
|
||||
|
||||
res = append(res, result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.File,
|
||||
Line: line,
|
||||
},
|
||||
Text: text,
|
||||
LineRange: r,
|
||||
FromLinter: gosecName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res = append(res, result.Issue{
|
||||
Pos: token.Position{
|
||||
Filename: i.File,
|
||||
Line: line,
|
||||
},
|
||||
Text: text,
|
||||
LineRange: r,
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
19
pkg/golinters/gosimple.go
Normal file
19
pkg/golinters/gosimple.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/simple"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
func NewGosimple() *goanalysis.Linter {
|
||||
analyzers := analyzersMapToSlice(simple.Analyzers)
|
||||
setAnalyzersGoVersion(analyzers)
|
||||
|
||||
return goanalysis.NewLinter(
|
||||
"gosimple",
|
||||
"Linter for Go source code that specializes in simplifying a code",
|
||||
analyzers,
|
||||
nil,
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
|
@ -171,5 +171,5 @@ func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter {
|
|||
"such as Printf calls whose arguments do not align with the format string",
|
||||
analyzersFromConfig(cfg),
|
||||
settings,
|
||||
)
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
ineffassignAPI "github.com/golangci/ineffassign"
|
||||
|
||||
|
@ -10,29 +14,50 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Ineffassign struct{}
|
||||
const ineffassignName = "ineffassign"
|
||||
|
||||
func (Ineffassign) Name() string {
|
||||
return "ineffassign"
|
||||
}
|
||||
func NewIneffassign() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Ineffassign) Desc() string {
|
||||
return "Detects when assignments to existing variables are not used"
|
||||
}
|
||||
|
||||
func (lint Ineffassign) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues := ineffassignAPI.Run(getAllFileNames(lintCtx))
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
ineffassignName,
|
||||
"Detects when assignments to existing variables are not used",
|
||||
[]*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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("ineffectual assignment to %s", formatCode(i.IdentName, lintCtx.Cfg)),
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
issues := ineffassignAPI.Run(fileNames)
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("ineffectual assignment to %s", formatCode(i.IdentName, lintCtx.Cfg)),
|
||||
FromLinter: ineffassignName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/buildssa"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
"mvdan.cc/interfacer/check"
|
||||
|
||||
|
@ -9,38 +14,56 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Interfacer struct{}
|
||||
const interfacerName = "interfacer"
|
||||
|
||||
func (Interfacer) Name() string {
|
||||
return "interfacer"
|
||||
}
|
||||
func NewInterfacer() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Interfacer) Desc() string {
|
||||
return "Linter that suggests narrower interface types"
|
||||
}
|
||||
|
||||
func (lint Interfacer) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
c := new(check.Checker)
|
||||
c.Program(lintCtx.Program)
|
||||
c.ProgramSSA(lintCtx.SSAProgram)
|
||||
|
||||
issues, err := c.Check()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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)
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
pos := lintCtx.SSAProgram.Fset.Position(i.Pos())
|
||||
res = append(res, result.Issue{
|
||||
Pos: pos,
|
||||
Text: i.Message(),
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
issues, err := c.Check()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
pos := pass.Fset.Position(i.Pos())
|
||||
res = append(res, result.Issue{
|
||||
Pos: pos,
|
||||
Text: i.Message(),
|
||||
FromLinter: interfacerName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -2,28 +2,21 @@ package golinters
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Lll struct{}
|
||||
|
||||
func (Lll) Name() string {
|
||||
return "lll"
|
||||
}
|
||||
|
||||
func (Lll) Desc() string {
|
||||
return "Reports long lines"
|
||||
}
|
||||
|
||||
func (lint Lll) getIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) {
|
||||
func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
|
||||
f, err := os.Open(filename)
|
||||
|
@ -45,7 +38,7 @@ func (lint Lll) getIssuesForFile(filename string, maxLineLen int, tabSpaces stri
|
|||
Line: lineNumber,
|
||||
},
|
||||
Text: fmt.Sprintf("line is %d characters", lineLen),
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: lllName,
|
||||
})
|
||||
}
|
||||
lineNumber++
|
||||
|
@ -70,7 +63,7 @@ func (lint Lll) getIssuesForFile(filename string, maxLineLen int, tabSpaces stri
|
|||
Column: 1,
|
||||
},
|
||||
Text: fmt.Sprintf("line is more than %d characters", bufio.MaxScanTokenSize),
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: lllName,
|
||||
})
|
||||
} else {
|
||||
return nil, fmt.Errorf("can't scan file %s: %s", filename, err)
|
||||
|
@ -80,16 +73,50 @@ func (lint Lll) getIssuesForFile(filename string, maxLineLen int, tabSpaces stri
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (lint Lll) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
spaces := strings.Repeat(" ", lintCtx.Settings().Lll.TabWidth)
|
||||
for _, f := range getAllFileNames(lintCtx) {
|
||||
issues, err := lint.getIssuesForFile(f, lintCtx.Settings().Lll.LineLength, spaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, issues...)
|
||||
}
|
||||
const lllName = "lll"
|
||||
|
||||
return res, nil
|
||||
func NewLLL() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
var res []result.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
|
||||
}
|
||||
res = append(res, issues...)
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,42 +1,58 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
malignedAPI "github.com/golangci/maligned"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Maligned struct{}
|
||||
|
||||
func (Maligned) Name() string {
|
||||
return "maligned"
|
||||
}
|
||||
|
||||
func (Maligned) Desc() string {
|
||||
return "Tool to detect Go structs that would take less memory if their fields were sorted"
|
||||
}
|
||||
|
||||
func (m Maligned) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues := malignedAPI.Run(lintCtx.Program)
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
func NewMaligned() *goanalysis.Linter {
|
||||
const linterName = "maligned"
|
||||
var mu sync.Mutex
|
||||
var res []result.Issue
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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)
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
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))
|
||||
malignedIssues := malignedAPI.Run(prog)
|
||||
if len(malignedIssues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
issues := make([]result.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, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: text,
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
res = append(res, issues...)
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: text,
|
||||
FromLinter: m.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return res
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,183 +1,16 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
|
||||
"honnef.co/go/tools/unused"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/simple"
|
||||
"honnef.co/go/tools/staticcheck"
|
||||
"honnef.co/go/tools/stylecheck"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
const (
|
||||
MegacheckParentName = "megacheck"
|
||||
MegacheckStaticcheckName = "staticcheck"
|
||||
MegacheckUnusedName = "unused"
|
||||
MegacheckGosimpleName = "gosimple"
|
||||
MegacheckStylecheckName = "stylecheck"
|
||||
)
|
||||
|
||||
var debugf = logutils.Debug("megacheck")
|
||||
|
||||
type Staticcheck struct {
|
||||
megacheck
|
||||
}
|
||||
|
||||
func NewStaticcheck() *Staticcheck {
|
||||
return &Staticcheck{
|
||||
megacheck: megacheck{
|
||||
staticcheckEnabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (Staticcheck) Name() string { return MegacheckStaticcheckName }
|
||||
func (Staticcheck) Desc() string {
|
||||
return "Staticcheck is a go vet on steroids, applying a ton of static analysis checks"
|
||||
}
|
||||
|
||||
type Gosimple struct {
|
||||
megacheck
|
||||
}
|
||||
|
||||
func NewGosimple() *Gosimple {
|
||||
return &Gosimple{
|
||||
megacheck: megacheck{
|
||||
gosimpleEnabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (Gosimple) Name() string { return MegacheckGosimpleName }
|
||||
func (Gosimple) Desc() string {
|
||||
return "Linter for Go source code that specializes in simplifying a code"
|
||||
}
|
||||
|
||||
type Unused struct {
|
||||
megacheck
|
||||
}
|
||||
|
||||
func NewUnused() *Unused {
|
||||
return &Unused{
|
||||
megacheck: megacheck{
|
||||
unusedEnabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (Unused) Name() string { return MegacheckUnusedName }
|
||||
func (Unused) Desc() string {
|
||||
return "Checks Go code for unused constants, variables, functions and types"
|
||||
}
|
||||
|
||||
type Stylecheck struct {
|
||||
megacheck
|
||||
}
|
||||
|
||||
func NewStylecheck() *Stylecheck {
|
||||
return &Stylecheck{
|
||||
megacheck: megacheck{
|
||||
stylecheckEnabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (Stylecheck) Name() string { return MegacheckStylecheckName }
|
||||
func (Stylecheck) Desc() string { return "Stylecheck is a replacement for golint" }
|
||||
|
||||
type megacheck struct {
|
||||
unusedEnabled bool
|
||||
gosimpleEnabled bool
|
||||
staticcheckEnabled bool
|
||||
stylecheckEnabled bool
|
||||
}
|
||||
|
||||
func (megacheck) Name() string {
|
||||
return MegacheckParentName
|
||||
}
|
||||
|
||||
func (megacheck) Desc() string {
|
||||
return "" // shouldn't be called
|
||||
}
|
||||
|
||||
func (m *megacheck) enableChildLinter(name string) error {
|
||||
switch name {
|
||||
case MegacheckStaticcheckName:
|
||||
m.staticcheckEnabled = true
|
||||
case MegacheckGosimpleName:
|
||||
m.gosimpleEnabled = true
|
||||
case MegacheckUnusedName:
|
||||
m.unusedEnabled = true
|
||||
case MegacheckStylecheckName:
|
||||
m.stylecheckEnabled = true
|
||||
default:
|
||||
return fmt.Errorf("invalid child linter name %s for metalinter %s", name, m.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MegacheckMetalinter struct{}
|
||||
|
||||
func (MegacheckMetalinter) Name() string {
|
||||
return MegacheckParentName
|
||||
}
|
||||
|
||||
func (MegacheckMetalinter) BuildLinterConfig(enabledChildren []string) (*linter.Config, error) {
|
||||
var m megacheck
|
||||
for _, name := range enabledChildren {
|
||||
if err := m.enableChildLinter(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: merge linter.Config and linter.Linter or refactor it in another way
|
||||
lc := &linter.Config{
|
||||
Linter: m,
|
||||
EnabledByDefault: false,
|
||||
NeedsSSARepr: false,
|
||||
InPresets: []string{linter.PresetStyle, linter.PresetBugs, linter.PresetUnused},
|
||||
Speed: 1,
|
||||
AlternativeNames: nil,
|
||||
OriginalURL: "",
|
||||
ParentLinterName: "",
|
||||
}
|
||||
if m.unusedEnabled {
|
||||
lc = lc.WithLoadDepsTypeInfo()
|
||||
} else {
|
||||
lc = lc.WithLoadForGoAnalysis()
|
||||
}
|
||||
return lc, nil
|
||||
}
|
||||
|
||||
func (MegacheckMetalinter) DefaultChildLinterNames() []string {
|
||||
// no stylecheck here for backwards compatibility for users who enabled megacheck: don't enable extra
|
||||
// linter for them
|
||||
return []string{MegacheckStaticcheckName, MegacheckGosimpleName, MegacheckUnusedName}
|
||||
}
|
||||
|
||||
func (m MegacheckMetalinter) AllChildLinterNames() []string {
|
||||
return append(m.DefaultChildLinterNames(), MegacheckStylecheckName)
|
||||
}
|
||||
|
||||
func (m megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
// Use OriginalPackages not Packages because `unused` doesn't work properly
|
||||
// when we deduplicate normal and test packages.
|
||||
return m.runMegacheck(ctx, lintCtx)
|
||||
}
|
||||
|
||||
func getAnalyzers(m map[string]*analysis.Analyzer) []*analysis.Analyzer {
|
||||
func analyzersMapToSlice(m map[string]*analysis.Analyzer) []*analysis.Analyzer {
|
||||
var ret []*analysis.Analyzer
|
||||
for _, v := range m {
|
||||
ret = append(ret, v)
|
||||
|
@ -185,7 +18,7 @@ func getAnalyzers(m map[string]*analysis.Analyzer) []*analysis.Analyzer {
|
|||
return ret
|
||||
}
|
||||
|
||||
func setGoVersion(analyzers []*analysis.Analyzer) {
|
||||
func setAnalyzersGoVersion(analyzers []*analysis.Analyzer) {
|
||||
const goVersion = 13 // TODO
|
||||
for _, a := range analyzers {
|
||||
if v := a.Flags.Lookup("go"); v != nil {
|
||||
|
@ -195,106 +28,3 @@ func setGoVersion(analyzers []*analysis.Analyzer) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m megacheck) runMegacheck(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var linters []linter.Linter
|
||||
|
||||
if m.gosimpleEnabled {
|
||||
analyzers := getAnalyzers(simple.Analyzers)
|
||||
setGoVersion(analyzers)
|
||||
lnt := goanalysis.NewLinter(MegacheckGosimpleName, "", analyzers, nil)
|
||||
linters = append(linters, lnt)
|
||||
}
|
||||
if m.staticcheckEnabled {
|
||||
analyzers := getAnalyzers(staticcheck.Analyzers)
|
||||
setGoVersion(analyzers)
|
||||
lnt := goanalysis.NewLinter(MegacheckStaticcheckName, "", analyzers, nil)
|
||||
linters = append(linters, lnt)
|
||||
}
|
||||
if m.stylecheckEnabled {
|
||||
analyzers := getAnalyzers(stylecheck.Analyzers)
|
||||
setGoVersion(analyzers)
|
||||
lnt := goanalysis.NewLinter(MegacheckStylecheckName, "", analyzers, nil)
|
||||
linters = append(linters, lnt)
|
||||
}
|
||||
|
||||
var u lint.CumulativeChecker
|
||||
if m.unusedEnabled {
|
||||
u = unused.NewChecker(lintCtx.Settings().Unused.CheckExported)
|
||||
analyzers := []*analysis.Analyzer{u.Analyzer()}
|
||||
setGoVersion(analyzers)
|
||||
lnt := goanalysis.NewLinter(MegacheckUnusedName, "", analyzers, nil)
|
||||
linters = append(linters, lnt)
|
||||
}
|
||||
|
||||
if len(linters) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var issues []result.Issue
|
||||
for _, lnt := range linters {
|
||||
i, err := lnt.Run(ctx, lintCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issues = append(issues, i...)
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
for _, ur := range u.Result() {
|
||||
p := u.ProblemObject(lintCtx.Packages[0].Fset, ur)
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: MegacheckUnusedName,
|
||||
Text: p.Message,
|
||||
Pos: p.Pos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (m megacheck) Analyzers() []*analysis.Analyzer {
|
||||
if m.unusedEnabled {
|
||||
// Don't treat this linter as go/analysis linter if unused is used
|
||||
// because it has non-standard API.
|
||||
return nil
|
||||
}
|
||||
|
||||
var allAnalyzers []*analysis.Analyzer
|
||||
if m.gosimpleEnabled {
|
||||
allAnalyzers = append(allAnalyzers, getAnalyzers(simple.Analyzers)...)
|
||||
}
|
||||
if m.staticcheckEnabled {
|
||||
allAnalyzers = append(allAnalyzers, getAnalyzers(staticcheck.Analyzers)...)
|
||||
}
|
||||
if m.stylecheckEnabled {
|
||||
allAnalyzers = append(allAnalyzers, getAnalyzers(stylecheck.Analyzers)...)
|
||||
}
|
||||
setGoVersion(allAnalyzers)
|
||||
return allAnalyzers
|
||||
}
|
||||
|
||||
func (megacheck) Cfg() map[string]map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m megacheck) AnalyzerToLinterNameMapping() map[*analysis.Analyzer]string {
|
||||
ret := map[*analysis.Analyzer]string{}
|
||||
if m.gosimpleEnabled {
|
||||
for _, a := range simple.Analyzers {
|
||||
ret[a] = MegacheckGosimpleName
|
||||
}
|
||||
}
|
||||
if m.staticcheckEnabled {
|
||||
for _, a := range staticcheck.Analyzers {
|
||||
ret[a] = MegacheckStaticcheckName
|
||||
}
|
||||
}
|
||||
if m.stylecheckEnabled {
|
||||
for _, a := range stylecheck.Analyzers {
|
||||
ret[a] = MegacheckStylecheckName
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -1,69 +1,20 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golangci/misspell"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Misspell struct{}
|
||||
|
||||
func NewMisspell() *Misspell {
|
||||
return &Misspell{}
|
||||
}
|
||||
|
||||
func (Misspell) Name() string {
|
||||
return "misspell"
|
||||
}
|
||||
|
||||
func (Misspell) Desc() string {
|
||||
return "Finds commonly misspelled English words in comments"
|
||||
}
|
||||
|
||||
func (lint Misspell) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
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":
|
||||
return nil, fmt.Errorf("unknown locale: %q", locale)
|
||||
}
|
||||
|
||||
if len(settings.IgnoreWords) != 0 {
|
||||
r.RemoveRule(settings.IgnoreWords)
|
||||
}
|
||||
|
||||
r.Compile()
|
||||
|
||||
var res []result.Issue
|
||||
for _, f := range getAllFileNames(lintCtx) {
|
||||
issues, err := lint.runOnFile(f, &r, lintCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, issues...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (lint Misspell) runOnFile(fileName string, r *misspell.Replacer, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
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 {
|
||||
|
@ -93,10 +44,87 @@ func (lint Misspell) runOnFile(fileName string, r *misspell.Replacer, lintCtx *l
|
|||
res = append(res, result.Issue{
|
||||
Pos: pos,
|
||||
Text: text,
|
||||
FromLinter: lint.Name(),
|
||||
FromLinter: misspellName,
|
||||
Replacement: replacement,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
const misspellName = "misspell"
|
||||
|
||||
func NewMisspell() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
var ruleErr error
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
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.Position(f.Pos())
|
||||
fileNames = append(fileNames, pos.Filename)
|
||||
}
|
||||
|
||||
var res []result.Issue
|
||||
for _, f := range fileNames {
|
||||
issues, err := runMisspellOnFile(f, &r, lintCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, issues...)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Nakedret struct{}
|
||||
|
||||
func (Nakedret) Name() string {
|
||||
return "nakedret"
|
||||
}
|
||||
|
||||
func (Nakedret) Desc() string {
|
||||
return "Finds naked returns in functions greater than a specified function length"
|
||||
}
|
||||
|
||||
type nakedretVisitor struct {
|
||||
maxLength int
|
||||
f *token.FileSet
|
||||
|
@ -50,7 +43,7 @@ func (v *nakedretVisitor) processFuncDecl(funcDecl *ast.FuncDecl) {
|
|||
}
|
||||
|
||||
v.issues = append(v.issues, result.Issue{
|
||||
FromLinter: Nakedret{}.Name(),
|
||||
FromLinter: nakedretName,
|
||||
Text: fmt.Sprintf("naked return in func `%s` with %d lines of code",
|
||||
funcDecl.Name.Name, functionLineLength),
|
||||
Pos: v.f.Position(s.Pos()),
|
||||
|
@ -85,16 +78,44 @@ func (v *nakedretVisitor) Visit(node ast.Node) ast.Visitor {
|
|||
return v
|
||||
}
|
||||
|
||||
func (lint Nakedret) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
v := nakedretVisitor{
|
||||
maxLength: lintCtx.Settings().Nakedret.MaxFuncLines,
|
||||
f: f.Fset,
|
||||
}
|
||||
ast.Walk(&v, f.F)
|
||||
res = append(res, v.issues...)
|
||||
}
|
||||
const nakedretName = "nakedret"
|
||||
|
||||
return res, nil
|
||||
func NewNakedret() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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 []result.Issue
|
||||
for _, file := range pass.Files {
|
||||
v := nakedretVisitor{
|
||||
maxLength: lintCtx.Settings().Nakedret.MaxFuncLines,
|
||||
f: pass.Fset,
|
||||
}
|
||||
ast.Walk(&v, file)
|
||||
res = append(res, v.issues...)
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,40 +1,57 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"sync"
|
||||
|
||||
"github.com/golangci/prealloc"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Prealloc struct{}
|
||||
const preallocName = "prealloc"
|
||||
|
||||
func (Prealloc) Name() string {
|
||||
return "prealloc"
|
||||
}
|
||||
func NewPrealloc() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Prealloc) Desc() string {
|
||||
return "Finds slice declarations that could potentially be preallocated"
|
||||
}
|
||||
|
||||
func (lint Prealloc) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
|
||||
s := &lintCtx.Settings().Prealloc
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
hints := prealloc.Check([]*ast.File{f.F}, s.Simple, s.RangeLoops, s.ForLoops)
|
||||
for _, hint := range hints {
|
||||
res = append(res, result.Issue{
|
||||
Pos: f.Fset.Position(hint.Pos),
|
||||
Text: fmt.Sprintf("Consider preallocating %s", formatCode(hint.DeclaredSliceName, lintCtx.Cfg)),
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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
|
||||
|
||||
return res, nil
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
var res []result.Issue
|
||||
hints := prealloc.Check(pass.Files, s.Simple, s.RangeLoops, s.ForLoops)
|
||||
for _, hint := range hints {
|
||||
res = append(res, result.Issue{
|
||||
Pos: pass.Fset.Position(hint.Pos),
|
||||
Text: fmt.Sprintf("Consider preallocating %s", formatCode(hint.DeclaredSliceName, lintCtx.Cfg)),
|
||||
FromLinter: preallocName,
|
||||
})
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -1,40 +1,60 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Scopelint struct{}
|
||||
const scopelintName = "scopelint"
|
||||
|
||||
func (Scopelint) Name() string {
|
||||
return "scopelint"
|
||||
}
|
||||
func NewScopelint() *goanalysis.Linter {
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Scopelint) Desc() string {
|
||||
return "Scopelint checks for unpinned variables in go programs"
|
||||
}
|
||||
|
||||
func (lint Scopelint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
var res []result.Issue
|
||||
|
||||
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
n := Node{
|
||||
fset: f.Fset,
|
||||
DangerObjects: map[*ast.Object]int{},
|
||||
UnsafeObjects: map[*ast.Object]int{},
|
||||
SkipFuncs: map[*ast.FuncLit]int{},
|
||||
issues: &res,
|
||||
}
|
||||
ast.Walk(&n, f.F)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
if len(res) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
||||
// The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e
|
||||
|
@ -151,6 +171,6 @@ func (f *Node) errorfAt(pos token.Position, format string, args ...interface{})
|
|||
*f.issues = append(*f.issues, result.Issue{
|
||||
Pos: pos,
|
||||
Text: fmt.Sprintf(format, args...),
|
||||
FromLinter: Scopelint{}.Name(),
|
||||
FromLinter: scopelintName,
|
||||
})
|
||||
}
|
||||
|
|
19
pkg/golinters/staticcheck.go
Normal file
19
pkg/golinters/staticcheck.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/staticcheck"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
func NewStaticcheck() *goanalysis.Linter {
|
||||
analyzers := analyzersMapToSlice(staticcheck.Analyzers)
|
||||
setAnalyzersGoVersion(analyzers)
|
||||
|
||||
return goanalysis.NewLinter(
|
||||
"staticcheck",
|
||||
"Staticcheck is a go vet on steroids, applying a ton of static analysis checks",
|
||||
analyzers,
|
||||
nil,
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
|
@ -1,38 +1,55 @@
|
|||
package golinters // nolint:dupl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
structcheckAPI "github.com/golangci/check/cmd/structcheck"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Structcheck struct{}
|
||||
|
||||
func (Structcheck) Name() string {
|
||||
return "structcheck"
|
||||
}
|
||||
|
||||
func (Structcheck) Desc() string {
|
||||
return "Finds unused struct fields"
|
||||
}
|
||||
|
||||
func (s Structcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues := structcheckAPI.Run(lintCtx.Program, lintCtx.Settings().Structcheck.CheckExportedFields)
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
func NewStructcheck() *goanalysis.Linter {
|
||||
const linterName = "structcheck"
|
||||
var mu sync.Mutex
|
||||
var res []result.Issue
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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)
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, lintCtx.Cfg)),
|
||||
FromLinter: s.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
structcheckIssues := structcheckAPI.Run(prog, checkExported)
|
||||
if len(structcheckIssues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
issues := make([]result.Issue, 0, len(structcheckIssues))
|
||||
for _, i := range structcheckIssues {
|
||||
issues = append(issues, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, lintCtx.Cfg)),
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
res = append(res, issues...)
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return res
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
19
pkg/golinters/stylecheck.go
Normal file
19
pkg/golinters/stylecheck.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/stylecheck"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
func NewStylecheck() *goanalysis.Linter {
|
||||
analyzers := analyzersMapToSlice(stylecheck.Analyzers)
|
||||
setAnalyzersGoVersion(analyzers)
|
||||
|
||||
return goanalysis.NewLinter(
|
||||
"stylecheck",
|
||||
"Stylecheck is a replacement for golint",
|
||||
analyzers,
|
||||
nil,
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
|
@ -1,57 +1,26 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
libpackages "github.com/golangci/golangci-lint/pkg/packages"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
type TypeCheck struct{}
|
||||
|
||||
func (TypeCheck) Name() string {
|
||||
return "typecheck"
|
||||
}
|
||||
|
||||
func (TypeCheck) Desc() string {
|
||||
return "Like the front-end of a Go compiler, parses and type-checks Go code"
|
||||
}
|
||||
|
||||
func (lint TypeCheck) parseError(srcErr packages.Error) (*result.Issue, error) {
|
||||
pos, err := libpackages.ParseErrorPosition(srcErr.Pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewTypecheck() *goanalysis.Linter {
|
||||
const linterName = "typecheck"
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
Run: func(pass *analysis.Pass) (interface{}, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
return &result.Issue{
|
||||
Pos: *pos,
|
||||
Text: srcErr.Msg,
|
||||
FromLinter: lint.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (lint TypeCheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
uniqReportedIssues := map[string]bool{}
|
||||
|
||||
var res []result.Issue
|
||||
for _, pkg := range lintCtx.NotCompilingPackages {
|
||||
errors := libpackages.ExtractErrors(pkg, lintCtx.ASTCache)
|
||||
for _, err := range errors {
|
||||
i, perr := lint.parseError(err)
|
||||
if perr != nil { // failed to parse
|
||||
if uniqReportedIssues[err.Msg] {
|
||||
continue
|
||||
}
|
||||
uniqReportedIssues[err.Msg] = true
|
||||
lintCtx.Log.Errorf("typechecking error: %s", err.Msg)
|
||||
} else {
|
||||
res = append(res, *i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
linter := goanalysis.NewLinter(
|
||||
linterName,
|
||||
"Like the front-end of a Go compiler, parses and type-checks Go code",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
linter.SetTypecheckMode()
|
||||
return linter
|
||||
}
|
||||
|
|
|
@ -1,38 +1,53 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
unconvertAPI "github.com/golangci/unconvert"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Unconvert struct{}
|
||||
|
||||
func (Unconvert) Name() string {
|
||||
return "unconvert"
|
||||
}
|
||||
|
||||
func (Unconvert) Desc() string {
|
||||
return "Remove unnecessary type conversions"
|
||||
}
|
||||
|
||||
func (lint Unconvert) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
positions := unconvertAPI.Run(lintCtx.Program)
|
||||
if len(positions) == 0 {
|
||||
return nil, nil
|
||||
func NewUnconvert() *goanalysis.Linter {
|
||||
const linterName = "unconvert"
|
||||
var mu sync.Mutex
|
||||
var res []result.Issue
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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)
|
||||
|
||||
res := make([]result.Issue, 0, len(positions))
|
||||
for _, pos := range positions {
|
||||
res = append(res, result.Issue{
|
||||
Pos: pos,
|
||||
Text: "unnecessary conversion",
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
positions := unconvertAPI.Run(prog)
|
||||
if len(positions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
issues := make([]result.Issue, 0, len(positions))
|
||||
for _, pos := range positions {
|
||||
issues = append(issues, result.Issue{
|
||||
Pos: pos,
|
||||
Text: "unnecessary conversion",
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
res = append(res, issues...)
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return res
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,49 +1,77 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/buildssa"
|
||||
"mvdan.cc/unparam/check"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Unparam struct{}
|
||||
func NewUnparam() *goanalysis.Linter {
|
||||
const linterName = "unparam"
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Unparam) Name() string {
|
||||
return "unparam"
|
||||
}
|
||||
|
||||
func (Unparam) Desc() string {
|
||||
return "Reports unused function parameters"
|
||||
}
|
||||
|
||||
func (lint Unparam) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
us := &lintCtx.Settings().Unparam
|
||||
|
||||
if us.Algo != "cha" {
|
||||
lintCtx.Log.Warnf("`linters-settings.unparam.algo` isn't supported by the newest `unparam`")
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
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`")
|
||||
}
|
||||
|
||||
c := &check.Checker{}
|
||||
c.CheckExportedFuncs(us.CheckExported)
|
||||
c.Packages(lintCtx.Packages)
|
||||
c.ProgramSSA(lintCtx.SSAProgram)
|
||||
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||
ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
|
||||
ssaPkg := ssa.Pkg
|
||||
|
||||
unparamIssues, err := c.Check()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg := &packages.Package{
|
||||
Fset: pass.Fset,
|
||||
Syntax: pass.Files,
|
||||
Types: pass.Pkg,
|
||||
TypesInfo: pass.TypesInfo,
|
||||
}
|
||||
|
||||
var res []result.Issue
|
||||
for _, i := range unparamIssues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: lintCtx.Program.Fset.Position(i.Pos()),
|
||||
Text: i.Message(),
|
||||
FromLinter: lint.Name(),
|
||||
})
|
||||
}
|
||||
c := &check.Checker{}
|
||||
c.CheckExportedFuncs(us.CheckExported)
|
||||
c.Packages([]*packages.Package{pkg})
|
||||
c.ProgramSSA(ssaPkg.Prog)
|
||||
|
||||
return res, nil
|
||||
unparamIssues, err := c.Check()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res []result.Issue
|
||||
for _, i := range unparamIssues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: pass.Fset.Position(i.Pos()),
|
||||
Text: i.Message(),
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
40
pkg/golinters/unused.go
Normal file
40
pkg/golinters/unused.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/unused"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
func NewUnused() *goanalysis.Linter {
|
||||
u := unused.NewChecker(false)
|
||||
analyzers := []*analysis.Analyzer{u.Analyzer()}
|
||||
setAnalyzersGoVersion(analyzers)
|
||||
|
||||
const name = "unused"
|
||||
lnt := goanalysis.NewLinter(
|
||||
name,
|
||||
"Checks Go code for unused constants, variables, functions and types",
|
||||
analyzers,
|
||||
nil,
|
||||
).WithIssuesReporter(func(lintCtx *linter.Context) []result.Issue {
|
||||
var issues []result.Issue
|
||||
for _, ur := range u.Result() {
|
||||
p := u.ProblemObject(lintCtx.Packages[0].Fset, ur)
|
||||
issues = append(issues, result.Issue{
|
||||
FromLinter: name,
|
||||
Text: p.Message,
|
||||
Pos: p.Pos,
|
||||
})
|
||||
}
|
||||
return issues
|
||||
}).WithContextSetter(func(lintCtx *linter.Context) {
|
||||
u.WholeProgram = lintCtx.Settings().Unused.CheckExported
|
||||
}).WithLoadMode(goanalysis.LoadModeWholeProgram)
|
||||
lnt.UseOriginalPackages()
|
||||
return lnt
|
||||
}
|
|
@ -2,14 +2,9 @@ package golinters
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
gopackages "golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
)
|
||||
|
||||
func formatCode(code string, _ *config.Config) string {
|
||||
|
@ -27,38 +22,3 @@ func formatCodeBlock(code string, _ *config.Config) string {
|
|||
|
||||
return fmt.Sprintf("```\n%s\n```", code)
|
||||
}
|
||||
|
||||
func getAllFileNames(ctx *linter.Context) []string {
|
||||
var ret []string
|
||||
uniqFiles := map[string]bool{} // files are duplicated for test packages
|
||||
for _, pkg := range ctx.Packages {
|
||||
for _, f := range pkg.GoFiles {
|
||||
if uniqFiles[f] {
|
||||
continue
|
||||
}
|
||||
uniqFiles[f] = true
|
||||
ret = append(ret, f)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getASTFilesForGoPkg(ctx *linter.Context, pkg *gopackages.Package) ([]*ast.File, *token.FileSet, error) {
|
||||
var files []*ast.File
|
||||
var fset *token.FileSet
|
||||
for _, filename := range pkg.GoFiles {
|
||||
f := ctx.ASTCache.Get(filename)
|
||||
if f == nil {
|
||||
return nil, nil, fmt.Errorf("no AST for file %s in cache: %+v", filename, *ctx.ASTCache)
|
||||
}
|
||||
|
||||
if f.Err != nil {
|
||||
return nil, nil, fmt.Errorf("can't load AST for file %s: %s", f.Name, f.Err)
|
||||
}
|
||||
|
||||
files = append(files, f.F)
|
||||
fset = f.Fset
|
||||
}
|
||||
|
||||
return files, fset, nil
|
||||
}
|
||||
|
|
|
@ -1,38 +1,55 @@
|
|||
package golinters // nolint:dupl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
varcheckAPI "github.com/golangci/check/cmd/varcheck"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type Varcheck struct{}
|
||||
|
||||
func (Varcheck) Name() string {
|
||||
return "varcheck"
|
||||
}
|
||||
|
||||
func (Varcheck) Desc() string {
|
||||
return "Finds unused global variables and constants"
|
||||
}
|
||||
|
||||
func (v Varcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
issues := varcheckAPI.Run(lintCtx.Program, lintCtx.Settings().Varcheck.CheckExportedFields)
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
func NewVarcheck() *goanalysis.Linter {
|
||||
const linterName = "varcheck"
|
||||
var mu sync.Mutex
|
||||
var res []result.Issue
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
linterName,
|
||||
"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)
|
||||
|
||||
res := make([]result.Issue, 0, len(issues))
|
||||
for _, i := range issues {
|
||||
res = append(res, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, lintCtx.Cfg)),
|
||||
FromLinter: v.Name(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
varcheckIssues := varcheckAPI.Run(prog, checkExported)
|
||||
if len(varcheckIssues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
issues := make([]result.Issue, 0, len(varcheckIssues))
|
||||
for _, i := range varcheckIssues {
|
||||
issues = append(issues, result.Issue{
|
||||
Pos: i.Pos,
|
||||
Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, lintCtx.Cfg)),
|
||||
FromLinter: linterName,
|
||||
})
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
res = append(res, issues...)
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return res
|
||||
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
||||
|
|
|
@ -1,71 +1,85 @@
|
|||
package golinters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/token"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
|
||||
"github.com/ultraware/whitespace"
|
||||
)
|
||||
|
||||
type Whitespace struct {
|
||||
}
|
||||
func NewWhitespace() *goanalysis.Linter {
|
||||
const linterName = "whitespace"
|
||||
var mu sync.Mutex
|
||||
var resIssues []result.Issue
|
||||
|
||||
func (Whitespace) Name() string {
|
||||
return "whitespace"
|
||||
}
|
||||
|
||||
func (Whitespace) Desc() string {
|
||||
return "Tool for detection of leading and trailing whitespace"
|
||||
}
|
||||
|
||||
func (w Whitespace) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||
settings := whitespace.Settings{MultiIf: lintCtx.Cfg.LintersSettings.Whitespace.MultiIf}
|
||||
|
||||
var issues []whitespace.Message
|
||||
for _, file := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||
issues = append(issues, whitespace.Run(file.F, file.Fset, settings)...)
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: goanalysis.TheOnlyAnalyzerName,
|
||||
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||
}
|
||||
return goanalysis.NewLinter(
|
||||
linterName,
|
||||
"Tool for detection of leading and trailing whitespace",
|
||||
[]*analysis.Analyzer{analyzer},
|
||||
nil,
|
||||
).WithContextSetter(func(lintCtx *linter.Context) {
|
||||
settings := whitespace.Settings{MultiIf: lintCtx.Cfg.LintersSettings.Whitespace.MultiIf}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
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)...)
|
||||
}
|
||||
|
||||
res := make([]result.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: w.Name(),
|
||||
Replacement: &result.Replacement{},
|
||||
if len(issues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := make([]result.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] = issue
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
resIssues = append(resIssues, res...)
|
||||
mu.Unlock()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
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] = issue
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
|
||||
return resIssues
|
||||
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@ func (c *Cache) parseFile(filePath string, fset *token.FileSet) {
|
|||
Name: filePath,
|
||||
}
|
||||
if err != nil {
|
||||
c.log.Warnf("Can't parse AST of %s: %s", filePath, err)
|
||||
c.log.Infof("Can't parse AST of %s: %s", filePath, err)
|
||||
// Info level because it will be reported by typecheck linter or go/analysis.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,12 @@ type Config struct {
|
|||
|
||||
LoadMode packages.LoadMode
|
||||
|
||||
NeedsSSARepr bool
|
||||
|
||||
InPresets []string
|
||||
Speed int // more value means faster execution of linter
|
||||
AlternativeNames []string
|
||||
|
||||
OriginalURL string // URL of original (not forked) repo, needed for autogenerated README
|
||||
ParentLinterName string // used only for megacheck's children now
|
||||
CanAutoFix bool
|
||||
IsSlow bool
|
||||
OriginalURL string // URL of original (not forked) repo, needed for autogenerated README
|
||||
CanAutoFix bool
|
||||
IsSlow bool
|
||||
}
|
||||
|
||||
func (lc *Config) ConsiderSlow() *Config {
|
||||
|
@ -48,24 +44,6 @@ func (lc *Config) WithLoadFiles() *Config {
|
|||
func (lc *Config) WithLoadForGoAnalysis() *Config {
|
||||
lc = lc.WithLoadFiles()
|
||||
lc.LoadMode |= packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedTypesSizes
|
||||
return lc.ConsiderSlow()
|
||||
}
|
||||
|
||||
func (lc *Config) WithLoadTypeInfo() *Config {
|
||||
lc = lc.WithLoadFiles()
|
||||
lc.LoadMode |= packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedTypesInfo | packages.NeedSyntax
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithLoadDepsTypeInfo() *Config {
|
||||
lc = lc.WithLoadTypeInfo()
|
||||
lc.LoadMode |= packages.NeedDeps
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithSSA() *Config {
|
||||
lc = lc.WithLoadDepsTypeInfo()
|
||||
lc.NeedsSSARepr = true
|
||||
return lc
|
||||
}
|
||||
|
||||
|
@ -74,11 +52,6 @@ func (lc *Config) WithPresets(presets ...string) *Config {
|
|||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithSpeed(speed int) *Config {
|
||||
lc.Speed = speed
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithURL(url string) *Config {
|
||||
lc.OriginalURL = url
|
||||
return lc
|
||||
|
@ -89,20 +62,11 @@ func (lc *Config) WithAlternativeNames(names ...string) *Config {
|
|||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithParent(parentLinterName string) *Config {
|
||||
lc.ParentLinterName = parentLinterName
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) WithAutoFix() *Config {
|
||||
lc.CanAutoFix = true
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *Config) GetSpeed() int {
|
||||
return lc.Speed
|
||||
}
|
||||
|
||||
func (lc *Config) AllNames() []string {
|
||||
return append([]string{lc.Name()}, lc.AlternativeNames...)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package linter
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load"
|
||||
|
||||
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
||||
|
@ -24,22 +23,14 @@ type Context struct {
|
|||
// version for each of packages
|
||||
OriginalPackages []*packages.Package
|
||||
|
||||
NotCompilingPackages []*packages.Package
|
||||
|
||||
LoaderConfig *loader.Config // deprecated, don't use for new linters
|
||||
Program *loader.Program // deprecated, use Packages for new linters
|
||||
|
||||
SSAProgram *ssa.Program // for unparam and interfacer but not for megacheck (it change it)
|
||||
|
||||
Cfg *config.Config
|
||||
ASTCache *astcache.Cache
|
||||
FileCache *fsutils.FileCache
|
||||
LineCache *fsutils.LineCache
|
||||
Log logutils.Log
|
||||
|
||||
PkgCache *pkgcache.Cache
|
||||
LoadGuard *load.Guard
|
||||
NeedWholeProgram bool
|
||||
PkgCache *pkgcache.Cache
|
||||
LoadGuard *load.Guard
|
||||
ASTCache *astcache.Cache
|
||||
}
|
||||
|
||||
func (c *Context) Settings() *config.LintersSettings {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package linter
|
||||
|
||||
type MetaLinter interface {
|
||||
Name() string
|
||||
BuildLinterConfig(enabledChildren []string) (*Config, error)
|
||||
AllChildLinterNames() []string
|
||||
DefaultChildLinterNames() []string
|
||||
}
|
|
@ -3,8 +3,6 @@ package lintersdb
|
|||
import (
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
|
@ -56,71 +54,30 @@ func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*lint
|
|||
// It should be after --presets to be able to run only fast linters in preset.
|
||||
// It should be before --enable and --disable to be able to enable or disable specific linter.
|
||||
if lcfg.Fast {
|
||||
for name := range resultLintersSet {
|
||||
if es.m.GetLinterConfig(name).IsSlowLinter() {
|
||||
for name, lc := range resultLintersSet {
|
||||
if lc.IsSlowLinter() {
|
||||
delete(resultLintersSet, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metaLinters := es.m.GetMetaLinters()
|
||||
|
||||
for _, name := range lcfg.Enable {
|
||||
if metaLinter := metaLinters[name]; metaLinter != nil {
|
||||
// e.g. if we use --enable=megacheck we should add staticcheck,unused and gosimple to result set
|
||||
for _, childLinter := range metaLinter.DefaultChildLinterNames() {
|
||||
resultLintersSet[childLinter] = es.m.GetLinterConfig(childLinter)
|
||||
}
|
||||
continue
|
||||
for _, lc := range es.m.GetLinterConfigs(name) {
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
resultLintersSet[lc.Name()] = lc
|
||||
}
|
||||
|
||||
lc := es.m.GetLinterConfig(name)
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
resultLintersSet[lc.Name()] = lc
|
||||
}
|
||||
|
||||
for _, name := range lcfg.Disable {
|
||||
if metaLinter := metaLinters[name]; metaLinter != nil {
|
||||
// e.g. if we use --disable=megacheck we should remove staticcheck,unused and gosimple from result set
|
||||
for _, childLinter := range metaLinter.DefaultChildLinterNames() {
|
||||
delete(resultLintersSet, childLinter)
|
||||
}
|
||||
continue
|
||||
for _, lc := range es.m.GetLinterConfigs(name) {
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
delete(resultLintersSet, lc.Name())
|
||||
}
|
||||
|
||||
lc := es.m.GetLinterConfig(name)
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
delete(resultLintersSet, lc.Name())
|
||||
}
|
||||
|
||||
return resultLintersSet
|
||||
}
|
||||
|
||||
func (es EnabledSet) optimizeLintersSet(linters map[string]*linter.Config) {
|
||||
for _, metaLinter := range es.m.GetMetaLinters() {
|
||||
var children []string
|
||||
for _, child := range metaLinter.AllChildLinterNames() {
|
||||
if _, ok := linters[child]; ok {
|
||||
children = append(children, child)
|
||||
}
|
||||
}
|
||||
|
||||
if len(children) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
delete(linters, child)
|
||||
}
|
||||
builtLinterConfig, err := metaLinter.BuildLinterConfig(children)
|
||||
if err != nil {
|
||||
panic("shouldn't fail during linter building: " + err.Error())
|
||||
}
|
||||
linters[metaLinter.Name()] = builtLinterConfig
|
||||
es.log.Infof("Optimized sublinters %s into metalinter %s", children, metaLinter.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (es EnabledSet) Get(optimize bool) ([]*linter.Config, error) {
|
||||
if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil {
|
||||
return nil, err
|
||||
|
@ -129,9 +86,8 @@ func (es EnabledSet) Get(optimize bool) ([]*linter.Config, error) {
|
|||
resultLintersSet := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
|
||||
es.verbosePrintLintersStatus(resultLintersSet)
|
||||
if optimize {
|
||||
es.optimizeLintersSet(resultLintersSet)
|
||||
es.combineGoAnalysisLinters(resultLintersSet)
|
||||
}
|
||||
es.combineGoAnalysisLinters(resultLintersSet)
|
||||
|
||||
var resultLinters []*linter.Config
|
||||
for _, lc := range resultLintersSet {
|
||||
|
@ -144,25 +100,19 @@ func (es EnabledSet) Get(optimize bool) ([]*linter.Config, error) {
|
|||
func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) {
|
||||
var goanalysisLinters []*goanalysis.Linter
|
||||
goanalysisPresets := map[string]bool{}
|
||||
analyzerToLinterName := map[*analysis.Analyzer]string{}
|
||||
for _, linter := range linters {
|
||||
lnt, ok := linter.Linter.(goanalysis.SupportedLinter)
|
||||
lnt, ok := linter.Linter.(*goanalysis.Linter)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
analyzers := lnt.Analyzers()
|
||||
if len(analyzers) == 0 {
|
||||
continue // e.g. if "unused" is enabled
|
||||
if lnt.LoadMode() == goanalysis.LoadModeWholeProgram {
|
||||
// It's ineffective by CPU and memory to run whole-program and incremental analyzers at once.
|
||||
continue
|
||||
}
|
||||
gl := goanalysis.NewLinter(linter.Name(), "", analyzers, lnt.Cfg())
|
||||
goanalysisLinters = append(goanalysisLinters, gl)
|
||||
goanalysisLinters = append(goanalysisLinters, lnt)
|
||||
for _, p := range linter.InPresets {
|
||||
goanalysisPresets[p] = true
|
||||
}
|
||||
for a, name := range lnt.AnalyzerToLinterNameMapping() {
|
||||
analyzerToLinterName[a] = name
|
||||
}
|
||||
}
|
||||
|
||||
if len(goanalysisLinters) <= 1 {
|
||||
|
@ -174,7 +124,7 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config)
|
|||
delete(linters, lnt.Name())
|
||||
}
|
||||
|
||||
ml := goanalysis.NewMetaLinter(goanalysisLinters, analyzerToLinterName)
|
||||
ml := goanalysis.NewMetaLinter(goanalysisLinters)
|
||||
|
||||
var presets []string
|
||||
for p := range goanalysisPresets {
|
||||
|
@ -184,13 +134,11 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config)
|
|||
mlConfig := &linter.Config{
|
||||
Linter: ml,
|
||||
EnabledByDefault: false,
|
||||
NeedsSSARepr: false,
|
||||
InPresets: presets,
|
||||
Speed: 5,
|
||||
AlternativeNames: nil,
|
||||
OriginalURL: "",
|
||||
ParentLinterName: "",
|
||||
}
|
||||
|
||||
mlConfig = mlConfig.WithLoadForGoAnalysis()
|
||||
|
||||
linters[ml.Name()] = mlConfig
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/golinters"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
|
@ -20,35 +18,36 @@ func TestGetEnabledLintersSet(t *testing.T) {
|
|||
def []string // enabled by default linters
|
||||
exp []string // alphabetically ordered enabled linter names
|
||||
}
|
||||
allMegacheckLinterNames := []string{"gosimple", "staticcheck", "unused"}
|
||||
cases := []cs{
|
||||
{
|
||||
cfg: config.Linters{
|
||||
Disable: []string{golinters.MegacheckMetalinter{}.Name()},
|
||||
Disable: []string{"megacheck"},
|
||||
},
|
||||
name: "disable all linters from megacheck",
|
||||
def: golinters.MegacheckMetalinter{}.DefaultChildLinterNames(),
|
||||
def: allMegacheckLinterNames,
|
||||
exp: nil, // all disabled
|
||||
},
|
||||
{
|
||||
cfg: config.Linters{
|
||||
Disable: []string{golinters.MegacheckStaticcheckName},
|
||||
Disable: []string{"staticcheck"},
|
||||
},
|
||||
name: "disable only staticcheck",
|
||||
def: golinters.MegacheckMetalinter{}.DefaultChildLinterNames(),
|
||||
exp: []string{golinters.MegacheckGosimpleName, golinters.MegacheckUnusedName},
|
||||
def: allMegacheckLinterNames,
|
||||
exp: []string{"gosimple", "unused"},
|
||||
},
|
||||
{
|
||||
name: "don't merge into megacheck",
|
||||
def: golinters.MegacheckMetalinter{}.DefaultChildLinterNames(),
|
||||
exp: golinters.MegacheckMetalinter{}.DefaultChildLinterNames(),
|
||||
def: allMegacheckLinterNames,
|
||||
exp: allMegacheckLinterNames,
|
||||
},
|
||||
{
|
||||
name: "expand megacheck",
|
||||
cfg: config.Linters{
|
||||
Enable: []string{golinters.MegacheckMetalinter{}.Name()},
|
||||
Enable: []string{"megacheck"},
|
||||
},
|
||||
def: nil,
|
||||
exp: golinters.MegacheckMetalinter{}.DefaultChildLinterNames(),
|
||||
exp: allMegacheckLinterNames,
|
||||
},
|
||||
{
|
||||
name: "don't disable anything",
|
||||
|
@ -99,9 +98,9 @@ func TestGetEnabledLintersSet(t *testing.T) {
|
|||
t.Run(c.name, func(t *testing.T) {
|
||||
var defaultLinters []*linter.Config
|
||||
for _, ln := range c.def {
|
||||
lc := m.GetLinterConfig(ln)
|
||||
assert.NotNil(t, lc, ln)
|
||||
defaultLinters = append(defaultLinters, lc)
|
||||
lcs := m.GetLinterConfigs(ln)
|
||||
assert.NotNil(t, lcs, ln)
|
||||
defaultLinters = append(defaultLinters, lcs...)
|
||||
}
|
||||
|
||||
els := es.build(&c.cfg, defaultLinters)
|
||||
|
|
|
@ -10,20 +10,20 @@ import (
|
|||
)
|
||||
|
||||
type Manager struct {
|
||||
nameToLC map[string]*linter.Config
|
||||
cfg *config.Config
|
||||
nameToLCs map[string][]*linter.Config
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewManager(cfg *config.Config) *Manager {
|
||||
m := &Manager{cfg: cfg}
|
||||
nameToLC := make(map[string]*linter.Config)
|
||||
nameToLCs := make(map[string][]*linter.Config)
|
||||
for _, lc := range m.GetAllSupportedLinterConfigs() {
|
||||
for _, name := range lc.AllNames() {
|
||||
nameToLC[name] = lc
|
||||
nameToLCs[name] = append(nameToLCs[name], lc)
|
||||
}
|
||||
}
|
||||
|
||||
m.nameToLC = nameToLC
|
||||
m.nameToLCs = nameToLCs
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -40,17 +40,8 @@ func (m Manager) allPresetsSet() map[string]bool {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (m Manager) GetMetaLinter(name string) linter.MetaLinter {
|
||||
return m.GetMetaLinters()[name]
|
||||
}
|
||||
|
||||
func (m Manager) GetLinterConfig(name string) *linter.Config {
|
||||
lc, ok := m.nameToLC[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return lc
|
||||
func (m Manager) GetLinterConfigs(name string) []*linter.Config {
|
||||
return m.nameToLCs[name]
|
||||
}
|
||||
|
||||
func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config) bool) []*linter.Config {
|
||||
|
@ -64,215 +55,169 @@ func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config)
|
|||
return ret
|
||||
}
|
||||
|
||||
func (Manager) GetMetaLinters() map[string]linter.MetaLinter {
|
||||
metaLinters := []linter.MetaLinter{
|
||||
golinters.MegacheckMetalinter{},
|
||||
}
|
||||
|
||||
ret := map[string]linter.MetaLinter{}
|
||||
for _, metaLinter := range metaLinters {
|
||||
ret[metaLinter.Name()] = metaLinter
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
|
||||
var govetCfg *config.GovetSettings
|
||||
if m.cfg != nil {
|
||||
govetCfg = &m.cfg.LintersSettings.Govet
|
||||
}
|
||||
const megacheckName = "megacheck"
|
||||
lcs := []*linter.Config{
|
||||
linter.NewConfig(golinters.NewGovet(govetCfg)).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(4).
|
||||
WithAlternativeNames("vet", "vetshadow").
|
||||
WithURL("https://golang.org/cmd/vet/"),
|
||||
linter.NewConfig(golinters.NewBodyclose()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetPerformance, linter.PresetBugs).
|
||||
WithSpeed(4).
|
||||
WithURL("https://github.com/timakin/bodyclose"),
|
||||
linter.NewConfig(golinters.Errcheck{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewErrcheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/kisielk/errcheck"),
|
||||
linter.NewConfig(golinters.Golint{}).
|
||||
linter.NewConfig(golinters.NewGolint()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(3).
|
||||
WithURL("https://github.com/golang/lint"),
|
||||
|
||||
linter.NewConfig(golinters.NewStaticcheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(2).
|
||||
WithAlternativeNames(megacheckName).
|
||||
WithURL("https://staticcheck.io/"),
|
||||
linter.NewConfig(golinters.NewUnused()).
|
||||
WithLoadDepsTypeInfo().
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(5).
|
||||
WithAlternativeNames(megacheckName).
|
||||
ConsiderSlow().
|
||||
WithURL("https://github.com/dominikh/go-tools/tree/master/unused"),
|
||||
linter.NewConfig(golinters.NewGosimple()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(5).
|
||||
WithAlternativeNames(megacheckName).
|
||||
WithURL("https://github.com/dominikh/go-tools/tree/master/simple"),
|
||||
linter.NewConfig(golinters.NewStylecheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(5).
|
||||
WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"),
|
||||
|
||||
linter.NewConfig(golinters.Gosec{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewGosec()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(8).
|
||||
WithURL("https://github.com/securego/gosec").
|
||||
WithAlternativeNames("gas"),
|
||||
linter.NewConfig(golinters.Structcheck{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewStructcheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/opennota/check"),
|
||||
linter.NewConfig(golinters.Varcheck{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewVarcheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/opennota/check"),
|
||||
linter.NewConfig(golinters.Interfacer{}).
|
||||
WithLoadDepsTypeInfo().
|
||||
WithSSA().
|
||||
linter.NewConfig(golinters.NewInterfacer()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(6).
|
||||
WithURL("https://github.com/mvdan/interfacer"),
|
||||
linter.NewConfig(golinters.Unconvert{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewUnconvert()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/mdempsky/unconvert"),
|
||||
linter.NewConfig(golinters.Ineffassign{}).
|
||||
linter.NewConfig(golinters.NewIneffassign()).
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(9).
|
||||
WithURL("https://github.com/gordonklaus/ineffassign"),
|
||||
linter.NewConfig(golinters.Dupl{}).
|
||||
linter.NewConfig(golinters.NewDupl()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(7).
|
||||
WithURL("https://github.com/mibk/dupl"),
|
||||
linter.NewConfig(golinters.Goconst{}).
|
||||
linter.NewConfig(golinters.NewGoconst()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(9).
|
||||
WithURL("https://github.com/jgautheron/goconst"),
|
||||
linter.NewConfig(golinters.Deadcode{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewDeadcode()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"),
|
||||
linter.NewConfig(golinters.Gocyclo{}).
|
||||
linter.NewConfig(golinters.NewGocyclo()).
|
||||
WithPresets(linter.PresetComplexity).
|
||||
WithSpeed(8).
|
||||
WithURL("https://github.com/alecthomas/gocyclo"),
|
||||
linter.NewConfig(golinters.TypeCheck{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewTypecheck()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(10).
|
||||
WithURL(""),
|
||||
|
||||
linter.NewConfig(golinters.Gofmt{}).
|
||||
linter.NewConfig(golinters.NewGofmt()).
|
||||
WithPresets(linter.PresetFormatting).
|
||||
WithSpeed(7).
|
||||
WithAutoFix().
|
||||
WithURL("https://golang.org/cmd/gofmt/"),
|
||||
linter.NewConfig(golinters.Gofmt{UseGoimports: true}).
|
||||
linter.NewConfig(golinters.NewGoimports()).
|
||||
WithPresets(linter.PresetFormatting).
|
||||
WithSpeed(5).
|
||||
WithAutoFix().
|
||||
WithURL("https://godoc.org/golang.org/x/tools/cmd/goimports"),
|
||||
linter.NewConfig(golinters.Maligned{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewMaligned()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetPerformance).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/mdempsky/maligned"),
|
||||
linter.NewConfig(golinters.Depguard{}).
|
||||
WithLoadTypeInfo().
|
||||
linter.NewConfig(golinters.NewDepguard()).
|
||||
WithLoadForGoAnalysis().
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(6).
|
||||
WithURL("https://github.com/OpenPeeDeeP/depguard"),
|
||||
linter.NewConfig(golinters.Misspell{}).
|
||||
linter.NewConfig(golinters.NewMisspell()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(7).
|
||||
WithAutoFix().
|
||||
WithURL("https://github.com/client9/misspell"),
|
||||
linter.NewConfig(golinters.Lll{}).
|
||||
linter.NewConfig(golinters.NewLLL()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/walle/lll"),
|
||||
linter.NewConfig(golinters.Unparam{}).
|
||||
linter.NewConfig(golinters.NewUnparam()).
|
||||
WithPresets(linter.PresetUnused).
|
||||
WithSpeed(3).
|
||||
WithLoadDepsTypeInfo().
|
||||
WithSSA().
|
||||
WithLoadForGoAnalysis().
|
||||
WithURL("https://github.com/mvdan/unparam"),
|
||||
linter.NewConfig(golinters.Dogsled{}).
|
||||
linter.NewConfig(golinters.NewDogsled()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/alexkohler/dogsled"),
|
||||
linter.NewConfig(golinters.Nakedret{}).
|
||||
linter.NewConfig(golinters.NewNakedret()).
|
||||
WithPresets(linter.PresetComplexity).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/alexkohler/nakedret"),
|
||||
linter.NewConfig(golinters.Prealloc{}).
|
||||
linter.NewConfig(golinters.NewPrealloc()).
|
||||
WithPresets(linter.PresetPerformance).
|
||||
WithSpeed(8).
|
||||
WithURL("https://github.com/alexkohler/prealloc"),
|
||||
linter.NewConfig(golinters.Scopelint{}).
|
||||
linter.NewConfig(golinters.NewScopelint()).
|
||||
WithPresets(linter.PresetBugs).
|
||||
WithSpeed(8).
|
||||
WithURL("https://github.com/kyoh86/scopelint"),
|
||||
linter.NewConfig(golinters.Gocritic{}).
|
||||
linter.NewConfig(golinters.NewGocritic()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(5).
|
||||
WithLoadTypeInfo().
|
||||
WithLoadForGoAnalysis().
|
||||
WithURL("https://github.com/go-critic/go-critic"),
|
||||
linter.NewConfig(golinters.Gochecknoinits{}).
|
||||
linter.NewConfig(golinters.NewGochecknoinits()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/leighmcculloch/gochecknoinits"),
|
||||
linter.NewConfig(golinters.Gochecknoglobals{}).
|
||||
linter.NewConfig(golinters.NewGochecknoglobals()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/leighmcculloch/gochecknoglobals"),
|
||||
linter.NewConfig(golinters.Godox{}).
|
||||
linter.NewConfig(golinters.NewGodox()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/matoous/godox"),
|
||||
linter.NewConfig(golinters.Funlen{}).
|
||||
linter.NewConfig(golinters.NewFunlen()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithURL("https://github.com/ultraware/funlen"),
|
||||
linter.NewConfig(golinters.Whitespace{}).
|
||||
linter.NewConfig(golinters.NewWhitespace()).
|
||||
WithPresets(linter.PresetStyle).
|
||||
WithSpeed(10).
|
||||
WithAutoFix().
|
||||
WithURL("https://github.com/ultraware/whitespace"),
|
||||
}
|
||||
|
||||
isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == ""
|
||||
enabledByDefault := map[string]bool{
|
||||
golinters.NewGovet(nil).Name(): true,
|
||||
golinters.Errcheck{}.Name(): true,
|
||||
golinters.Staticcheck{}.Name(): true,
|
||||
golinters.Unused{}.Name(): true,
|
||||
golinters.Gosimple{}.Name(): true,
|
||||
golinters.Structcheck{}.Name(): true,
|
||||
golinters.Varcheck{}.Name(): true,
|
||||
golinters.Ineffassign{}.Name(): true,
|
||||
golinters.Deadcode{}.Name(): true,
|
||||
golinters.NewGovet(nil).Name(): true,
|
||||
golinters.NewErrcheck().Name(): true,
|
||||
golinters.NewStaticcheck().Name(): true,
|
||||
golinters.NewUnused().Name(): true,
|
||||
golinters.NewGosimple().Name(): true,
|
||||
golinters.NewStructcheck().Name(): true,
|
||||
golinters.NewVarcheck().Name(): true,
|
||||
golinters.NewIneffassign().Name(): true,
|
||||
golinters.NewDeadcode().Name(): true,
|
||||
|
||||
// don't typecheck for golangci.com: too many troubles
|
||||
golinters.TypeCheck{}.Name(): isLocalRun,
|
||||
golinters.NewTypecheck().Name(): isLocalRun,
|
||||
}
|
||||
return enableLinterConfigs(lcs, func(lc *linter.Config) bool {
|
||||
return enabledByDefault[lc.Name()]
|
||||
|
|
|
@ -21,7 +21,7 @@ func (v Validator) validateLintersNames(cfg *config.Linters) error {
|
|||
allNames := append([]string{}, cfg.Enable...)
|
||||
allNames = append(allNames, cfg.Disable...)
|
||||
for _, name := range allNames {
|
||||
if v.m.GetLinterConfig(name) == nil && v.m.GetMetaLinter(name) == nil {
|
||||
if v.m.GetLinterConfigs(name) == nil {
|
||||
return fmt.Errorf("no such linter %q", name)
|
||||
}
|
||||
}
|
||||
|
|
163
pkg/lint/load.go
163
pkg/lint/load.go
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -19,10 +18,7 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
|
@ -72,84 +68,6 @@ func (cl *ContextLoader) prepareBuildContext() {
|
|||
build.Default.BuildTags = cl.cfg.Run.BuildTags
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) makeFakeLoaderPackageInfo(pkg *packages.Package) *loader.PackageInfo {
|
||||
var errs []error
|
||||
for _, err := range pkg.Errors {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
typeInfo := &types.Info{}
|
||||
if pkg.TypesInfo != nil {
|
||||
typeInfo = pkg.TypesInfo
|
||||
}
|
||||
|
||||
return &loader.PackageInfo{
|
||||
Pkg: pkg.Types,
|
||||
Importable: true, // not used
|
||||
TransitivelyErrorFree: !pkg.IllTyped,
|
||||
|
||||
// use compiled (preprocessed) go files AST;
|
||||
// AST linters use not preprocessed go files AST
|
||||
Files: pkg.Syntax,
|
||||
Errors: errs,
|
||||
Info: *typeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) makeFakeLoaderProgram(pkgs []*packages.Package) *loader.Program {
|
||||
var createdPkgs []*loader.PackageInfo
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.IllTyped {
|
||||
// some linters crash on packages with errors,
|
||||
// skip them and warn about them in another place
|
||||
continue
|
||||
}
|
||||
|
||||
pkgInfo := cl.makeFakeLoaderPackageInfo(pkg)
|
||||
createdPkgs = append(createdPkgs, pkgInfo)
|
||||
}
|
||||
|
||||
allPkgs := map[*types.Package]*loader.PackageInfo{}
|
||||
for _, pkg := range createdPkgs {
|
||||
pkg := pkg
|
||||
allPkgs[pkg.Pkg] = pkg
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.IllTyped {
|
||||
// some linters crash on packages with errors,
|
||||
// skip them and warn about them in another place
|
||||
continue
|
||||
}
|
||||
|
||||
for _, impPkg := range pkg.Imports {
|
||||
// don't use astcache for imported packages: we don't find issues in cgo imported deps
|
||||
pkgInfo := cl.makeFakeLoaderPackageInfo(impPkg)
|
||||
allPkgs[pkgInfo.Pkg] = pkgInfo
|
||||
}
|
||||
}
|
||||
|
||||
return &loader.Program{
|
||||
Fset: pkgs[0].Fset,
|
||||
Imported: nil, // not used without .Created in any linter
|
||||
Created: createdPkgs, // all initial packages
|
||||
AllPackages: allPkgs, // all initial packages and their depndencies
|
||||
}
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) buildSSAProgram(pkgs []*packages.Package) *ssa.Program {
|
||||
startedAt := time.Now()
|
||||
var pkgsBuiltDuration time.Duration
|
||||
defer func() {
|
||||
cl.log.Infof("SSA repr building timing: packages building %s, total %s",
|
||||
pkgsBuiltDuration, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
ssaProg, _ := ssautil.Packages(pkgs, ssa.GlobalDebug)
|
||||
pkgsBuiltDuration = time.Since(startedAt)
|
||||
ssaProg.Build()
|
||||
return ssaProg
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) findLoadMode(linters []*linter.Config) packages.LoadMode {
|
||||
loadMode := packages.LoadMode(0)
|
||||
for _, lc := range linters {
|
||||
|
@ -305,13 +223,13 @@ func (cl *ContextLoader) loadPackages(ctx context.Context, loadMode packages.Loa
|
|||
return cl.filterTestMainPackages(pkgs), nil
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) tryParseTestPackage(pkg *packages.Package) (name, testName string, isTest bool) {
|
||||
func (cl *ContextLoader) tryParseTestPackage(pkg *packages.Package) (name string, isTest bool) {
|
||||
matches := cl.pkgTestIDRe.FindStringSubmatch(pkg.ID)
|
||||
if matches == nil {
|
||||
return "", "", false
|
||||
return "", false
|
||||
}
|
||||
|
||||
return matches[1], matches[2], true
|
||||
return matches[1], true
|
||||
}
|
||||
|
||||
func (cl *ContextLoader) filterTestMainPackages(pkgs []*packages.Package) []*packages.Package {
|
||||
|
@ -332,7 +250,7 @@ func (cl *ContextLoader) filterTestMainPackages(pkgs []*packages.Package) []*pac
|
|||
func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*packages.Package {
|
||||
packagesWithTests := map[string]bool{}
|
||||
for _, pkg := range pkgs {
|
||||
name, _, isTest := cl.tryParseTestPackage(pkg)
|
||||
name, isTest := cl.tryParseTestPackage(pkg)
|
||||
if !isTest {
|
||||
continue
|
||||
}
|
||||
|
@ -343,7 +261,7 @@ func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*pa
|
|||
|
||||
var retPkgs []*packages.Package
|
||||
for _, pkg := range pkgs {
|
||||
_, _, isTest := cl.tryParseTestPackage(pkg)
|
||||
_, isTest := cl.tryParseTestPackage(pkg)
|
||||
if !isTest && packagesWithTests[pkg.PkgPath] {
|
||||
// If tests loading is enabled,
|
||||
// for package with files a.go and a_test.go go/packages loads two packages:
|
||||
|
@ -361,15 +279,6 @@ func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*pa
|
|||
return retPkgs
|
||||
}
|
||||
|
||||
func needSSA(linters []*linter.Config) bool {
|
||||
for _, lc := range linters {
|
||||
if lc.NeedsSSARepr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (cl *ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*linter.Context, error) {
|
||||
loadMode := cl.findLoadMode(linters)
|
||||
|
@ -384,21 +293,13 @@ func (cl *ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*l
|
|||
return nil, exitcodes.ErrNoGoFiles
|
||||
}
|
||||
|
||||
var prog *loader.Program
|
||||
if loadMode&packages.NeedTypes != 0 {
|
||||
prog = cl.makeFakeLoaderProgram(deduplicatedPkgs)
|
||||
}
|
||||
|
||||
var ssaProg *ssa.Program
|
||||
if needSSA(linters) {
|
||||
ssaProg = cl.buildSSAProgram(deduplicatedPkgs)
|
||||
}
|
||||
|
||||
astLog := cl.log.Child("astcache")
|
||||
startedLoadingASTAt := time.Now()
|
||||
astCache, err := astcache.LoadFromPackages(deduplicatedPkgs, astLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cl.log.Infof("Loaded %d AST files in %s", len(astCache.ParsedFilenames()), time.Since(startedLoadingASTAt))
|
||||
|
||||
ret := &linter.Context{
|
||||
Packages: deduplicatedPkgs,
|
||||
|
@ -407,50 +308,14 @@ func (cl *ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*l
|
|||
// see https://github.com/golangci/golangci-lint/pull/585.
|
||||
OriginalPackages: pkgs,
|
||||
|
||||
Program: prog,
|
||||
SSAProgram: ssaProg,
|
||||
LoaderConfig: &loader.Config{
|
||||
Cwd: "", // used by depguard and fallbacked to os.Getcwd
|
||||
Build: nil, // used by depguard and megacheck and fallbacked to build.Default
|
||||
},
|
||||
Cfg: cl.cfg,
|
||||
ASTCache: astCache,
|
||||
Log: cl.log,
|
||||
FileCache: cl.fileCache,
|
||||
LineCache: cl.lineCache,
|
||||
PkgCache: cl.pkgCache,
|
||||
LoadGuard: cl.loadGuard,
|
||||
NeedWholeProgram: loadMode&packages.NeedDeps != 0 && loadMode&packages.NeedTypesInfo != 0,
|
||||
Cfg: cl.cfg,
|
||||
ASTCache: astCache,
|
||||
Log: cl.log,
|
||||
FileCache: cl.fileCache,
|
||||
LineCache: cl.lineCache,
|
||||
PkgCache: cl.pkgCache,
|
||||
LoadGuard: cl.loadGuard,
|
||||
}
|
||||
|
||||
separateNotCompilingPackages(ret)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// separateNotCompilingPackages moves not compiling packages into separate slice:
|
||||
// a lot of linters crash on such packages
|
||||
func separateNotCompilingPackages(lintCtx *linter.Context) {
|
||||
// Separate deduplicated packages
|
||||
goodPkgs := make([]*packages.Package, 0, len(lintCtx.Packages))
|
||||
for _, pkg := range lintCtx.Packages {
|
||||
if pkg.IllTyped {
|
||||
lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, pkg)
|
||||
} else {
|
||||
goodPkgs = append(goodPkgs, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
lintCtx.Packages = goodPkgs
|
||||
if len(lintCtx.NotCompilingPackages) != 0 {
|
||||
lintCtx.Log.Infof("Packages that do not compile: %+v", lintCtx.NotCompilingPackages)
|
||||
}
|
||||
|
||||
// Separate original (not deduplicated) packages
|
||||
goodOriginalPkgs := make([]*packages.Package, 0, len(lintCtx.OriginalPackages))
|
||||
for _, pkg := range lintCtx.OriginalPackages {
|
||||
if !pkg.IllTyped {
|
||||
goodOriginalPkgs = append(goodOriginalPkgs, pkg)
|
||||
}
|
||||
}
|
||||
lintCtx.OriginalPackages = goodOriginalPkgs
|
||||
}
|
||||
|
|
|
@ -4,10 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/errorutil"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
|
@ -93,12 +90,6 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
|
|||
}, nil
|
||||
}
|
||||
|
||||
type lintRes struct {
|
||||
linter *linter.Config
|
||||
err error
|
||||
issues []result.Issue
|
||||
}
|
||||
|
||||
func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context,
|
||||
lc *linter.Config) (ret []result.Issue, err error) {
|
||||
defer func() {
|
||||
|
@ -127,148 +118,40 @@ func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context,
|
|||
return issues, nil
|
||||
}
|
||||
|
||||
func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context,
|
||||
tasksCh <-chan *linter.Config, lintResultsCh chan<- lintRes, name string) {
|
||||
sw := timeutils.NewStopwatch(name, r.Log)
|
||||
defer sw.Print()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case lc, ok := <-tasksCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
// XXX: if check it in only int a select
|
||||
// it's possible to not enter to this case until tasksCh is empty.
|
||||
return
|
||||
}
|
||||
var issues []result.Issue
|
||||
var err error
|
||||
sw.TrackStage(lc.Name(), func() {
|
||||
issues, err = r.runLinterSafe(ctx, lintCtx, lc)
|
||||
})
|
||||
lintResultsCh <- lintRes{
|
||||
linter: lc,
|
||||
err: err,
|
||||
issues: issues,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r Runner) logWorkersStat(workersFinishTimes []time.Time) {
|
||||
lastFinishTime := workersFinishTimes[0]
|
||||
for _, t := range workersFinishTimes {
|
||||
if t.After(lastFinishTime) {
|
||||
lastFinishTime = t
|
||||
}
|
||||
}
|
||||
|
||||
logStrings := []string{}
|
||||
for i, t := range workersFinishTimes {
|
||||
if t.Equal(lastFinishTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
logStrings = append(logStrings, fmt.Sprintf("#%d: %s", i+1, lastFinishTime.Sub(t)))
|
||||
}
|
||||
|
||||
r.Log.Infof("Workers idle times: %s", strings.Join(logStrings, ", "))
|
||||
}
|
||||
|
||||
func getSortedLintersConfigs(linters []*linter.Config) []*linter.Config {
|
||||
ret := make([]*linter.Config, len(linters))
|
||||
copy(ret, linters)
|
||||
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return ret[i].GetSpeed() < ret[j].GetSpeed()
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *Runner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []*linter.Config) <-chan lintRes {
|
||||
tasksCh := make(chan *linter.Config, len(linters))
|
||||
lintResultsCh := make(chan lintRes, len(linters))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
workersFinishTimes := make([]time.Time, lintCtx.Cfg.Run.Concurrency)
|
||||
|
||||
for i := 0; i < lintCtx.Cfg.Run.Concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
name := fmt.Sprintf("worker.%d", i+1)
|
||||
r.runWorker(ctx, lintCtx, tasksCh, lintResultsCh, name)
|
||||
workersFinishTimes[i] = time.Now()
|
||||
}(i)
|
||||
}
|
||||
|
||||
lcs := getSortedLintersConfigs(linters)
|
||||
for _, lc := range lcs {
|
||||
tasksCh <- lc
|
||||
}
|
||||
close(tasksCh)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(lintResultsCh)
|
||||
|
||||
r.logWorkersStat(workersFinishTimes)
|
||||
}()
|
||||
|
||||
return lintResultsCh
|
||||
}
|
||||
|
||||
type processorStat struct {
|
||||
inCount int
|
||||
outCount int
|
||||
}
|
||||
|
||||
func (r Runner) processLintResults(inCh <-chan lintRes) <-chan lintRes {
|
||||
outCh := make(chan lintRes, 64)
|
||||
func (r Runner) processLintResults(inIssues []result.Issue) []result.Issue {
|
||||
sw := timeutils.NewStopwatch("processing", r.Log)
|
||||
|
||||
go func() {
|
||||
sw := timeutils.NewStopwatch("processing", r.Log)
|
||||
var issuesBefore, issuesAfter int
|
||||
statPerProcessor := map[string]processorStat{}
|
||||
|
||||
var issuesBefore, issuesAfter int
|
||||
statPerProcessor := map[string]processorStat{}
|
||||
defer close(outCh)
|
||||
var outIssues []result.Issue
|
||||
if len(inIssues) != 0 {
|
||||
issuesBefore += len(inIssues)
|
||||
outIssues = r.processIssues(inIssues, sw, statPerProcessor)
|
||||
issuesAfter += len(outIssues)
|
||||
}
|
||||
|
||||
for res := range inCh {
|
||||
if res.err != nil {
|
||||
r.Log.Warnf("Can't run linter %s: %s", res.linter.Name(), res.err)
|
||||
continue
|
||||
}
|
||||
// finalize processors: logging, clearing, no heavy work here
|
||||
|
||||
if len(res.issues) != 0 {
|
||||
issuesBefore += len(res.issues)
|
||||
res.issues = r.processIssues(res.issues, sw, statPerProcessor)
|
||||
issuesAfter += len(res.issues)
|
||||
outCh <- res
|
||||
}
|
||||
}
|
||||
for _, p := range r.Processors {
|
||||
p := p
|
||||
sw.TrackStage(p.Name(), func() {
|
||||
p.Finish()
|
||||
})
|
||||
}
|
||||
|
||||
// finalize processors: logging, clearing, no heavy work here
|
||||
if issuesBefore != issuesAfter {
|
||||
r.Log.Infof("Issues before processing: %d, after processing: %d", issuesBefore, issuesAfter)
|
||||
}
|
||||
r.printPerProcessorStat(statPerProcessor)
|
||||
sw.PrintStages()
|
||||
|
||||
for _, p := range r.Processors {
|
||||
p := p
|
||||
sw.TrackStage(p.Name(), func() {
|
||||
p.Finish()
|
||||
})
|
||||
}
|
||||
|
||||
if issuesBefore != issuesAfter {
|
||||
r.Log.Infof("Issues before processing: %d, after processing: %d", issuesBefore, issuesAfter)
|
||||
}
|
||||
r.printPerProcessorStat(statPerProcessor)
|
||||
sw.PrintStages()
|
||||
}()
|
||||
|
||||
return outCh
|
||||
return outIssues
|
||||
}
|
||||
|
||||
func (r Runner) printPerProcessorStat(stat map[string]processorStat) {
|
||||
|
@ -283,40 +166,24 @@ func (r Runner) printPerProcessorStat(stat map[string]processorStat) {
|
|||
}
|
||||
}
|
||||
|
||||
func collectIssues(resCh <-chan lintRes) <-chan result.Issue {
|
||||
retIssues := make(chan result.Issue, 1024)
|
||||
go func() {
|
||||
defer close(retIssues)
|
||||
func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *linter.Context) []result.Issue {
|
||||
sw := timeutils.NewStopwatch("linters", r.Log)
|
||||
defer sw.Print()
|
||||
|
||||
for res := range resCh {
|
||||
if len(res.issues) == 0 {
|
||||
continue
|
||||
var issues []result.Issue
|
||||
for _, lc := range linters {
|
||||
lc := lc
|
||||
sw.TrackStage(lc.Name(), func() {
|
||||
linterIssues, err := r.runLinterSafe(ctx, lintCtx, lc)
|
||||
if err != nil {
|
||||
r.Log.Warnf("Can't run linter %s: %s", lc.Linter.Name(), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range res.issues {
|
||||
retIssues <- i
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return retIssues
|
||||
}
|
||||
|
||||
func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *linter.Context) <-chan result.Issue {
|
||||
lintResultsCh := r.runWorkers(ctx, lintCtx, linters)
|
||||
processedLintResultsCh := r.processLintResults(lintResultsCh)
|
||||
if ctx.Err() != nil {
|
||||
// XXX: always process issues, even if timeout occurred
|
||||
finishedLintersN := 0
|
||||
for range processedLintResultsCh {
|
||||
finishedLintersN++
|
||||
}
|
||||
|
||||
r.Log.Errorf("%d/%d linters finished: deadline exceeded",
|
||||
finishedLintersN, len(linters))
|
||||
issues = append(issues, linterIssues...)
|
||||
})
|
||||
}
|
||||
|
||||
return collectIssues(processedLintResultsCh)
|
||||
return r.processLintResults(issues)
|
||||
}
|
||||
|
||||
func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, statPerProcessor map[string]processorStat) []result.Issue {
|
||||
|
|
|
@ -36,14 +36,14 @@ func NewCheckstyle() *Checkstyle {
|
|||
return &Checkstyle{}
|
||||
}
|
||||
|
||||
func (Checkstyle) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
func (Checkstyle) Print(ctx context.Context, issues []result.Issue) error {
|
||||
out := checkstyleOutput{
|
||||
Version: "5.0",
|
||||
}
|
||||
|
||||
files := map[string]*checkstyleFile{}
|
||||
|
||||
for issue := range issues {
|
||||
for _, issue := range issues {
|
||||
file, ok := files[issue.FilePath()]
|
||||
if !ok {
|
||||
file = &checkstyleFile{
|
||||
|
|
|
@ -30,9 +30,9 @@ func NewCodeClimate() *CodeClimate {
|
|||
return &CodeClimate{}
|
||||
}
|
||||
|
||||
func (p CodeClimate) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
func (p CodeClimate) Print(ctx context.Context, issues []result.Issue) error {
|
||||
allIssues := []CodeClimateIssue{}
|
||||
for i := range issues {
|
||||
for _, i := range issues {
|
||||
var issue CodeClimateIssue
|
||||
issue.Description = i.FromLinter + ": " + i.Text
|
||||
issue.Location.Path = i.Pos.Filename
|
||||
|
|
|
@ -25,14 +25,9 @@ type JSONResult struct {
|
|||
Report *report.Data
|
||||
}
|
||||
|
||||
func (p JSON) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
allIssues := []result.Issue{}
|
||||
for i := range issues {
|
||||
allIssues = append(allIssues, i)
|
||||
}
|
||||
|
||||
func (p JSON) Print(ctx context.Context, issues []result.Issue) error {
|
||||
res := JSONResult{
|
||||
Issues: allIssues,
|
||||
Issues: issues,
|
||||
Report: p.rd,
|
||||
}
|
||||
|
||||
|
|
|
@ -38,10 +38,10 @@ func NewJunitXML() *JunitXML {
|
|||
return &JunitXML{}
|
||||
}
|
||||
|
||||
func (JunitXML) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
func (JunitXML) Print(ctx context.Context, issues []result.Issue) error {
|
||||
suites := make(map[string]testSuiteXML) // use a map to group by file
|
||||
|
||||
for i := range issues {
|
||||
for _, i := range issues {
|
||||
suiteName := i.FilePath()
|
||||
testSuite := suites[suiteName]
|
||||
testSuite.Suite = i.FilePath()
|
||||
|
|
|
@ -7,5 +7,5 @@ import (
|
|||
)
|
||||
|
||||
type Printer interface {
|
||||
Print(ctx context.Context, issues <-chan result.Issue) error
|
||||
Print(ctx context.Context, issues []result.Issue) error
|
||||
}
|
||||
|
|
|
@ -29,10 +29,10 @@ func (p Tab) SprintfColored(ca color.Attribute, format string, args ...interface
|
|||
return c.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func (p *Tab) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
func (p *Tab) Print(ctx context.Context, issues []result.Issue) error {
|
||||
w := tabwriter.NewWriter(logutils.StdOut, 0, 0, 2, ' ', 0)
|
||||
|
||||
for i := range issues {
|
||||
for _, i := range issues {
|
||||
i := i
|
||||
p.printIssue(&i, w)
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ func (p Text) SprintfColored(ca color.Attribute, format string, args ...interfac
|
|||
return c.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||
for i := range issues {
|
||||
func (p *Text) Print(ctx context.Context, issues []result.Issue) error {
|
||||
for _, i := range issues {
|
||||
i := i
|
||||
p.printIssue(&i)
|
||||
|
||||
|
|
|
@ -40,43 +40,37 @@ func (f Fixer) printStat() {
|
|||
f.sw.PrintStages()
|
||||
}
|
||||
|
||||
func (f Fixer) Process(issues <-chan result.Issue) <-chan result.Issue {
|
||||
func (f Fixer) Process(issues []result.Issue) []result.Issue {
|
||||
if !f.cfg.Issues.NeedFix {
|
||||
return issues
|
||||
}
|
||||
|
||||
outCh := make(chan result.Issue, 1024)
|
||||
|
||||
go func() {
|
||||
issuesToFixPerFile := map[string][]result.Issue{}
|
||||
for issue := range issues {
|
||||
if issue.Replacement == nil {
|
||||
outCh <- issue
|
||||
continue
|
||||
}
|
||||
|
||||
issuesToFixPerFile[issue.FilePath()] = append(issuesToFixPerFile[issue.FilePath()], issue)
|
||||
outIssues := make([]result.Issue, 0, len(issues))
|
||||
issuesToFixPerFile := map[string][]result.Issue{}
|
||||
for _, issue := range issues {
|
||||
if issue.Replacement == nil {
|
||||
outIssues = append(outIssues, issue)
|
||||
continue
|
||||
}
|
||||
|
||||
for file, issuesToFix := range issuesToFixPerFile {
|
||||
var err error
|
||||
f.sw.TrackStage("all", func() {
|
||||
err = f.fixIssuesInFile(file, issuesToFix) //nolint:scopelint
|
||||
})
|
||||
if err != nil {
|
||||
f.log.Errorf("Failed to fix issues in file %s: %s", file, err)
|
||||
issuesToFixPerFile[issue.FilePath()] = append(issuesToFixPerFile[issue.FilePath()], issue)
|
||||
}
|
||||
|
||||
// show issues only if can't fix them
|
||||
for _, issue := range issuesToFix {
|
||||
outCh <- issue
|
||||
}
|
||||
}
|
||||
for file, issuesToFix := range issuesToFixPerFile {
|
||||
var err error
|
||||
f.sw.TrackStage("all", func() {
|
||||
err = f.fixIssuesInFile(file, issuesToFix) //nolint:scopelint
|
||||
})
|
||||
if err != nil {
|
||||
f.log.Errorf("Failed to fix issues in file %s: %s", file, err)
|
||||
|
||||
// show issues only if can't fix them
|
||||
outIssues = append(outIssues, issuesToFix...)
|
||||
}
|
||||
f.printStat()
|
||||
close(outCh)
|
||||
}()
|
||||
}
|
||||
|
||||
return outCh
|
||||
f.printStat()
|
||||
return outIssues
|
||||
}
|
||||
|
||||
func (f Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error {
|
||||
|
|
|
@ -7,8 +7,6 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
|
@ -95,7 +93,8 @@ func (p *Nolint) getOrCreateFileData(i *result.Issue) (*fileData, error) {
|
|||
i.FilePath(), p.astCache.ParsedFilenames())
|
||||
}
|
||||
if file.Err != nil {
|
||||
return nil, errors.Wrapf(file.Err, "can't parse file %s", i.FilePath())
|
||||
// Don't report error because it's already must be reporter by typecheck or go/analysis.
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
fd.ignoredRanges = p.buildIgnoredRangesForFile(file.F, file.Fset, i.FilePath())
|
||||
|
@ -221,22 +220,17 @@ func (p *Nolint) extractInlineRangeFromComment(text string, g ast.Node, fset *to
|
|||
var gotUnknownLinters bool
|
||||
for _, linter := range linterItems {
|
||||
linterName := strings.ToLower(strings.TrimSpace(linter))
|
||||
metaLinter := p.dbManager.GetMetaLinter(linterName)
|
||||
if metaLinter != nil {
|
||||
// user can set metalinter name in nolint directive (e.g. megacheck), then
|
||||
// we should add to nolint all the metalinter's default children
|
||||
linters = append(linters, metaLinter.DefaultChildLinterNames()...)
|
||||
continue
|
||||
}
|
||||
|
||||
lc := p.dbManager.GetLinterConfig(linterName)
|
||||
if lc == nil {
|
||||
lcs := p.dbManager.GetLinterConfigs(linterName)
|
||||
if lcs == nil {
|
||||
p.unknownLintersSet[linterName] = true
|
||||
gotUnknownLinters = true
|
||||
continue
|
||||
}
|
||||
|
||||
linters = append(linters, lc.Name()) // normalize name to work with aliases
|
||||
for _, lc := range lcs {
|
||||
linters = append(linters, lc.Name()) // normalize name to work with aliases
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnknownLinters {
|
||||
|
|
|
@ -164,8 +164,8 @@ func TestEnabledLinters(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "fast option combined with enable and enable-all",
|
||||
args: "--enable-all --fast --enable=staticcheck",
|
||||
el: getAllFastLintersWith("staticcheck"),
|
||||
args: "--enable-all --fast --enable=unused",
|
||||
el: getAllFastLintersWith("unused"),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
}
|
||||
|
|
20
vendor/github.com/golangci/gofmt/gofmt/gofmt.go
generated
vendored
20
vendor/github.com/golangci/gofmt/gofmt/gofmt.go
generated
vendored
|
@ -21,6 +21,7 @@ import (
|
|||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -42,10 +43,11 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
fileSet = token.NewFileSet() // per process FileSet
|
||||
exitCode = 0
|
||||
rewrite func(*ast.File) *ast.File
|
||||
parserMode parser.Mode
|
||||
fileSet = token.NewFileSet() // per process FileSet
|
||||
exitCode = 0
|
||||
rewrite func(*ast.File) *ast.File
|
||||
parserMode parser.Mode
|
||||
parserModeInitOnce sync.Once
|
||||
)
|
||||
|
||||
func report(err error) {
|
||||
|
@ -59,10 +61,12 @@ func usage() {
|
|||
}
|
||||
|
||||
func initParserMode() {
|
||||
parserMode = parser.ParseComments
|
||||
if *allErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
parserModeInitOnce.Do(func() {
|
||||
parserMode = parser.ParseComments
|
||||
if *allErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func isGoFile(f os.FileInfo) bool {
|
||||
|
|
96
vendor/github.com/golangci/lint-1/lint.go
generated
vendored
96
vendor/github.com/golangci/lint-1/lint.go
generated
vendored
|
@ -241,7 +241,6 @@ func (f *file) lint() {
|
|||
f.lintBlankImports()
|
||||
f.lintExported()
|
||||
f.lintNames()
|
||||
f.lintVarDecls()
|
||||
f.lintElses()
|
||||
f.lintRanges()
|
||||
f.lintErrorf()
|
||||
|
@ -471,11 +470,26 @@ func (f *file) lintPackageComment() {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *file) isCgo() bool {
|
||||
if f.src == nil {
|
||||
return false
|
||||
}
|
||||
newLinePos := bytes.Index(f.src, []byte("\n"))
|
||||
if newLinePos < 0 {
|
||||
return false
|
||||
}
|
||||
firstLine := string(f.src[:newLinePos])
|
||||
|
||||
// files using cgo have implicitly added comment "Created by cgo - DO NOT EDIT" for go <= 1.10
|
||||
// and "Code generated by cmd/cgo" for go >= 1.11
|
||||
return strings.Contains(firstLine, "Created by cgo") || strings.Contains(firstLine, "Code generated by cmd/cgo")
|
||||
}
|
||||
|
||||
// lintBlankImports complains if a non-main package has blank imports that are
|
||||
// not documented.
|
||||
func (f *file) lintBlankImports() {
|
||||
// In package main and in tests, we don't complain about blank imports.
|
||||
if f.pkg.main || f.isTest() {
|
||||
if f.pkg.main || f.isTest() || f.isCgo() {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1020,84 +1034,6 @@ var zeroLiteral = map[string]bool{
|
|||
"0i": true,
|
||||
}
|
||||
|
||||
// lintVarDecls examines variable declarations. It complains about declarations with
|
||||
// redundant LHS types that can be inferred from the RHS.
|
||||
func (f *file) lintVarDecls() {
|
||||
var lastGen *ast.GenDecl // last GenDecl entered.
|
||||
|
||||
f.walk(func(node ast.Node) bool {
|
||||
switch v := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
if v.Tok != token.CONST && v.Tok != token.VAR {
|
||||
return false
|
||||
}
|
||||
lastGen = v
|
||||
return true
|
||||
case *ast.ValueSpec:
|
||||
if lastGen.Tok == token.CONST {
|
||||
return false
|
||||
}
|
||||
if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 {
|
||||
return false
|
||||
}
|
||||
rhs := v.Values[0]
|
||||
// An underscore var appears in a common idiom for compile-time interface satisfaction,
|
||||
// as in "var _ Interface = (*Concrete)(nil)".
|
||||
if isIdent(v.Names[0], "_") {
|
||||
return false
|
||||
}
|
||||
// If the RHS is a zero value, suggest dropping it.
|
||||
zero := false
|
||||
if lit, ok := rhs.(*ast.BasicLit); ok {
|
||||
zero = zeroLiteral[lit.Value]
|
||||
} else if isIdent(rhs, "nil") {
|
||||
zero = true
|
||||
}
|
||||
if zero {
|
||||
f.errorf(rhs, 0.9, category("zero-value"), "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0])
|
||||
return false
|
||||
}
|
||||
lhsTyp := f.pkg.typeOf(v.Type)
|
||||
rhsTyp := f.pkg.typeOf(rhs)
|
||||
|
||||
if !validType(lhsTyp) || !validType(rhsTyp) {
|
||||
// Type checking failed (often due to missing imports).
|
||||
return false
|
||||
}
|
||||
|
||||
if !types.Identical(lhsTyp, rhsTyp) {
|
||||
// Assignment to a different type is not redundant.
|
||||
return false
|
||||
}
|
||||
|
||||
// The next three conditions are for suppressing the warning in situations
|
||||
// where we were unable to typecheck.
|
||||
|
||||
// If the LHS type is an interface, don't warn, since it is probably a
|
||||
// concrete type on the RHS. Note that our feeble lexical check here
|
||||
// will only pick up interface{} and other literal interface types;
|
||||
// that covers most of the cases we care to exclude right now.
|
||||
if _, ok := v.Type.(*ast.InterfaceType); ok {
|
||||
return false
|
||||
}
|
||||
// If the RHS is an untyped const, only warn if the LHS type is its default type.
|
||||
if defType, ok := f.isUntypedConst(rhs); ok && !isIdent(v.Type, defType) {
|
||||
return false
|
||||
}
|
||||
|
||||
f.errorf(v.Type, 0.8, category("type-inference"), "should omit type %s from declaration of var %s; it will be inferred from the right-hand side", f.render(v.Type), v.Names[0])
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func validType(T types.Type) bool {
|
||||
return T != nil &&
|
||||
T != types.Typ[types.Invalid] &&
|
||||
!strings.Contains(T.String(), "invalid type") // good but not foolproof
|
||||
}
|
||||
|
||||
// lintElses examines else blocks. It complains about any else block whose if block ends in a return.
|
||||
func (f *file) lintElses() {
|
||||
// We don't want to flag if { } else if { } else { } constructions.
|
||||
|
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
|
@ -63,12 +63,12 @@ github.com/golangci/go-misc/deadcode
|
|||
github.com/golangci/goconst
|
||||
# github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
|
||||
github.com/golangci/gocyclo/pkg/gocyclo
|
||||
# github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98
|
||||
# github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a
|
||||
github.com/golangci/gofmt/gofmt
|
||||
github.com/golangci/gofmt/goimports
|
||||
# github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc
|
||||
github.com/golangci/ineffassign
|
||||
# github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217
|
||||
# github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89
|
||||
github.com/golangci/lint-1
|
||||
# github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca
|
||||
github.com/golangci/maligned
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue