
1. Allow govet to work in 2 modes: fast and slow. Default is slow. In fast mode golangci-lint runs `go install -i` and `go test -i` for analyzed packages. But it's fast only when: - go >= 1.10 - it's repeated run or $GOPATH/pkg or `go env GOCACHE` is cached between CI builds In slow mode we load program from source code like for another linters and do it only once for all linters. 3. Patch govet code to warn about any troubles with the type information. Default behaviour of govet was to hide such warnings. Fail analysis if there are any troubles with type loading: it will prevent false-positives and false-negatives from govet. 4. Describe almost all options in .golangci.example.yml and include it into README. Describe when to use slow or fast mode of govet. 5. Speed up govet: reuse AST parsing: it's already parsed once by golangci-lint. For "slow" runs (when we run at least one slow linter) speedup by not loading type information second time. 6. Improve logging, debug logging 7. Fix crash in logging of AST cache warnings (#118)
432 lines
12 KiB
Go
432 lines
12 KiB
Go
package lintersdb
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/config"
|
|
"github.com/golangci/golangci-lint/pkg/golinters"
|
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
)
|
|
|
|
func AllPresets() []string {
|
|
return []string{linter.PresetBugs, linter.PresetUnused, linter.PresetFormatting, linter.PresetStyle, linter.PresetComplexity, linter.PresetPerformance}
|
|
}
|
|
|
|
func allPresetsSet() map[string]bool {
|
|
ret := map[string]bool{}
|
|
for _, p := range AllPresets() {
|
|
ret[p] = true
|
|
}
|
|
return ret
|
|
}
|
|
|
|
var nameToLC map[string]linter.Config
|
|
var nameToLCOnce sync.Once
|
|
|
|
func getLinterConfig(name string) *linter.Config {
|
|
nameToLCOnce.Do(func() {
|
|
nameToLC = make(map[string]linter.Config)
|
|
for _, lc := range GetAllSupportedLinterConfigs() {
|
|
nameToLC[lc.Linter.Name()] = lc
|
|
}
|
|
})
|
|
|
|
lc, ok := nameToLC[name]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return &lc
|
|
}
|
|
|
|
func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config) bool) []linter.Config {
|
|
var ret []linter.Config
|
|
for _, lc := range lcs {
|
|
lc.EnabledByDefault = isEnabled(&lc)
|
|
ret = append(ret, lc)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func GetAllSupportedLinterConfigs() []linter.Config {
|
|
lcs := []linter.Config{
|
|
linter.NewConfig(golinters.Govet{}).
|
|
WithFullImport(). // TODO: depend on it's configuration here
|
|
WithPresets(linter.PresetBugs).
|
|
WithSpeed(4).
|
|
WithURL("https://golang.org/cmd/vet/"),
|
|
linter.NewConfig(golinters.Errcheck{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetBugs).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/kisielk/errcheck"),
|
|
linter.NewConfig(golinters.Golint{}).
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(3).
|
|
WithURL("https://github.com/golang/lint"),
|
|
|
|
linter.NewConfig(golinters.Megacheck{StaticcheckEnabled: true}).
|
|
WithSSA().
|
|
WithPresets(linter.PresetBugs).
|
|
WithSpeed(2).
|
|
WithURL("https://staticcheck.io/"),
|
|
linter.NewConfig(golinters.Megacheck{UnusedEnabled: true}).
|
|
WithSSA().
|
|
WithPresets(linter.PresetUnused).
|
|
WithSpeed(5).
|
|
WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/unused"),
|
|
linter.NewConfig(golinters.Megacheck{GosimpleEnabled: true}).
|
|
WithSSA().
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(5).
|
|
WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/gosimple"),
|
|
|
|
linter.NewConfig(golinters.Gas{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetBugs).
|
|
WithSpeed(8).
|
|
WithURL("https://github.com/GoASTScanner/gas"),
|
|
linter.NewConfig(golinters.Structcheck{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetUnused).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/opennota/check"),
|
|
linter.NewConfig(golinters.Varcheck{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetUnused).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/opennota/check"),
|
|
linter.NewConfig(golinters.Interfacer{}).
|
|
WithSSA().
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(6).
|
|
WithURL("https://github.com/mvdan/interfacer"),
|
|
linter.NewConfig(golinters.Unconvert{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/mdempsky/unconvert"),
|
|
linter.NewConfig(golinters.Ineffassign{}).
|
|
WithPresets(linter.PresetUnused).
|
|
WithSpeed(9).
|
|
WithURL("https://github.com/gordonklaus/ineffassign"),
|
|
linter.NewConfig(golinters.Dupl{}).
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(7).
|
|
WithURL("https://github.com/mibk/dupl"),
|
|
linter.NewConfig(golinters.Goconst{}).
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(9).
|
|
WithURL("https://github.com/jgautheron/goconst"),
|
|
linter.NewConfig(golinters.Deadcode{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetUnused).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"),
|
|
linter.NewConfig(golinters.Gocyclo{}).
|
|
WithPresets(linter.PresetComplexity).
|
|
WithSpeed(8).
|
|
WithURL("https://github.com/alecthomas/gocyclo"),
|
|
linter.NewConfig(golinters.TypeCheck{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetBugs).
|
|
WithSpeed(10).
|
|
WithURL(""),
|
|
|
|
linter.NewConfig(golinters.Gofmt{}).
|
|
WithPresets(linter.PresetFormatting).
|
|
WithSpeed(7).
|
|
WithURL("https://golang.org/cmd/gofmt/"),
|
|
linter.NewConfig(golinters.Gofmt{UseGoimports: true}).
|
|
WithPresets(linter.PresetFormatting).
|
|
WithSpeed(5).
|
|
WithURL("https://godoc.org/golang.org/x/tools/cmd/goimports"),
|
|
linter.NewConfig(golinters.Maligned{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetPerformance).
|
|
WithSpeed(10).
|
|
WithURL("https://github.com/mdempsky/maligned"),
|
|
linter.NewConfig(golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}).
|
|
WithSSA().
|
|
WithPresets(linter.PresetStyle, linter.PresetBugs, linter.PresetUnused).
|
|
WithSpeed(1).
|
|
WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/megacheck"),
|
|
linter.NewConfig(golinters.Depguard{}).
|
|
WithFullImport().
|
|
WithPresets(linter.PresetStyle).
|
|
WithSpeed(6).
|
|
WithURL("https://github.com/OpenPeeDeeP/depguard"),
|
|
}
|
|
|
|
if os.Getenv("GOLANGCI_COM_RUN") == "1" {
|
|
disabled := map[string]bool{
|
|
golinters.Gocyclo{}.Name(): true, // annoying
|
|
golinters.Dupl{}.Name(): true, // annoying
|
|
golinters.Maligned{}.Name(): true, // rarely usable
|
|
golinters.TypeCheck{}.Name(): true, // annoying because of different building envs
|
|
}
|
|
return enableLinterConfigs(lcs, func(lc *linter.Config) bool {
|
|
return !disabled[lc.Linter.Name()]
|
|
})
|
|
}
|
|
|
|
enabled := map[string]bool{
|
|
golinters.Govet{}.Name(): true,
|
|
golinters.Errcheck{}.Name(): true,
|
|
golinters.Megacheck{StaticcheckEnabled: true}.Name(): true,
|
|
golinters.Megacheck{UnusedEnabled: true}.Name(): true,
|
|
golinters.Megacheck{GosimpleEnabled: true}.Name(): true,
|
|
golinters.Gas{}.Name(): true,
|
|
golinters.Structcheck{}.Name(): true,
|
|
golinters.Varcheck{}.Name(): true,
|
|
golinters.Ineffassign{}.Name(): true,
|
|
golinters.Deadcode{}.Name(): true,
|
|
golinters.TypeCheck{}.Name(): true,
|
|
}
|
|
return enableLinterConfigs(lcs, func(lc *linter.Config) bool {
|
|
return enabled[lc.Linter.Name()]
|
|
})
|
|
}
|
|
|
|
func GetAllEnabledByDefaultLinters() []linter.Config {
|
|
var ret []linter.Config
|
|
for _, lc := range GetAllSupportedLinterConfigs() {
|
|
if lc.EnabledByDefault {
|
|
ret = append(ret, lc)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func linterConfigsToMap(lcs []linter.Config) map[string]*linter.Config {
|
|
ret := map[string]*linter.Config{}
|
|
for _, lc := range lcs {
|
|
lc := lc // local copy
|
|
ret[lc.Linter.Name()] = &lc
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func validateLintersNames(cfg *config.Linters) error {
|
|
allNames := append([]string{}, cfg.Enable...)
|
|
allNames = append(allNames, cfg.Disable...)
|
|
for _, name := range allNames {
|
|
if getLinterConfig(name) == nil {
|
|
return fmt.Errorf("no such linter %q", name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validatePresets(cfg *config.Linters) error {
|
|
allPresets := allPresetsSet()
|
|
for _, p := range cfg.Presets {
|
|
if !allPresets[p] {
|
|
return fmt.Errorf("no such preset %q: only next presets exist: (%s)", p, strings.Join(AllPresets(), "|"))
|
|
}
|
|
}
|
|
|
|
if len(cfg.Presets) != 0 && cfg.EnableAll {
|
|
return fmt.Errorf("--presets is incompatible with --enable-all")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateAllDisableEnableOptions(cfg *config.Linters) error {
|
|
if cfg.EnableAll && cfg.DisableAll {
|
|
return fmt.Errorf("--enable-all and --disable-all options must not be combined")
|
|
}
|
|
|
|
if cfg.DisableAll {
|
|
if len(cfg.Enable) == 0 && len(cfg.Presets) == 0 {
|
|
return fmt.Errorf("all linters were disabled, but no one linter was enabled: must enable at least one")
|
|
}
|
|
|
|
if len(cfg.Disable) != 0 {
|
|
return fmt.Errorf("can't combine options --disable-all and --disable %s", cfg.Disable[0])
|
|
}
|
|
}
|
|
|
|
if cfg.EnableAll && len(cfg.Enable) != 0 && !cfg.Fast {
|
|
return fmt.Errorf("can't combine options --enable-all and --enable %s", cfg.Enable[0])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateDisabledAndEnabledAtOneMoment(cfg *config.Linters) error {
|
|
enabledLintersSet := map[string]bool{}
|
|
for _, name := range cfg.Enable {
|
|
enabledLintersSet[name] = true
|
|
}
|
|
|
|
for _, name := range cfg.Disable {
|
|
if enabledLintersSet[name] {
|
|
return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateEnabledDisabledLintersConfig(cfg *config.Linters) error {
|
|
validators := []func(cfg *config.Linters) error{
|
|
validateLintersNames,
|
|
validatePresets,
|
|
validateAllDisableEnableOptions,
|
|
validateDisabledAndEnabledAtOneMoment,
|
|
}
|
|
for _, v := range validators {
|
|
if err := v(cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetAllLinterConfigsForPreset(p string) []linter.Config {
|
|
ret := []linter.Config{}
|
|
for _, lc := range GetAllSupportedLinterConfigs() {
|
|
for _, ip := range lc.InPresets {
|
|
if p == ip {
|
|
ret = append(ret, lc)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []linter.Config) map[string]*linter.Config { // nolint:gocyclo
|
|
resultLintersSet := map[string]*linter.Config{}
|
|
switch {
|
|
case len(lcfg.Presets) != 0:
|
|
break // imply --disable-all
|
|
case lcfg.EnableAll:
|
|
resultLintersSet = linterConfigsToMap(GetAllSupportedLinterConfigs())
|
|
case lcfg.DisableAll:
|
|
break
|
|
default:
|
|
resultLintersSet = linterConfigsToMap(enabledByDefaultLinters)
|
|
}
|
|
|
|
// --presets can only add linters to default set
|
|
for _, p := range lcfg.Presets {
|
|
for _, lc := range GetAllLinterConfigsForPreset(p) {
|
|
lc := lc
|
|
resultLintersSet[lc.Linter.Name()] = &lc
|
|
}
|
|
}
|
|
|
|
// --fast removes slow linters from current set.
|
|
// 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 getLinterConfig(name).DoesFullImport {
|
|
delete(resultLintersSet, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, name := range lcfg.Enable {
|
|
resultLintersSet[name] = getLinterConfig(name)
|
|
}
|
|
|
|
for _, name := range lcfg.Disable {
|
|
if name == "megacheck" {
|
|
for _, ln := range getAllMegacheckSubLinterNames() {
|
|
delete(resultLintersSet, ln)
|
|
}
|
|
}
|
|
delete(resultLintersSet, name)
|
|
}
|
|
|
|
optimizeLintersSet(resultLintersSet)
|
|
return resultLintersSet
|
|
}
|
|
|
|
func getAllMegacheckSubLinterNames() []string {
|
|
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
|
|
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
|
|
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
|
|
return []string{unusedName, gosimpleName, staticcheckName}
|
|
}
|
|
|
|
func optimizeLintersSet(linters map[string]*linter.Config) {
|
|
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
|
|
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
|
|
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
|
|
fullName := golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}.Name()
|
|
allNames := []string{unusedName, gosimpleName, staticcheckName, fullName}
|
|
|
|
megacheckCount := 0
|
|
for _, n := range allNames {
|
|
if linters[n] != nil {
|
|
megacheckCount++
|
|
}
|
|
}
|
|
|
|
if megacheckCount <= 1 {
|
|
return
|
|
}
|
|
|
|
isFullEnabled := linters[fullName] != nil
|
|
m := golinters.Megacheck{
|
|
UnusedEnabled: isFullEnabled || linters[unusedName] != nil,
|
|
GosimpleEnabled: isFullEnabled || linters[gosimpleName] != nil,
|
|
StaticcheckEnabled: isFullEnabled || linters[staticcheckName] != nil,
|
|
}
|
|
|
|
for _, n := range allNames {
|
|
delete(linters, n)
|
|
}
|
|
|
|
lc := *getLinterConfig("megacheck")
|
|
lc.Linter = m
|
|
linters[m.Name()] = &lc
|
|
}
|
|
|
|
func GetEnabledLinters(cfg *config.Config, log logutils.Log) ([]linter.Config, error) {
|
|
if err := validateEnabledDisabledLintersConfig(&cfg.Linters); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resultLintersSet := getEnabledLintersSet(&cfg.Linters, GetAllEnabledByDefaultLinters())
|
|
|
|
var resultLinters []linter.Config
|
|
for _, lc := range resultLintersSet {
|
|
resultLinters = append(resultLinters, *lc)
|
|
}
|
|
|
|
verbosePrintLintersStatus(cfg, resultLinters, log)
|
|
|
|
return resultLinters, nil
|
|
}
|
|
|
|
func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config, log logutils.Log) {
|
|
var linterNames []string
|
|
for _, lc := range lcs {
|
|
linterNames = append(linterNames, lc.Linter.Name())
|
|
}
|
|
sort.StringSlice(linterNames).Sort()
|
|
log.Infof("Active %d linters: %s", len(linterNames), linterNames)
|
|
|
|
if len(cfg.Linters.Presets) != 0 {
|
|
sort.StringSlice(cfg.Linters.Presets).Sort()
|
|
log.Infof("Active presets: %s", cfg.Linters.Presets)
|
|
}
|
|
}
|