golangci-lint/pkg/lint/lintersdb/enabled_set.go
Isaev Denis 6a979fb40d
Update staticcheck and cache go/analysis facts (#699)
* update staticcheck

Don't fork staticcheck: use the upstream version.
Remove unneeded SSA loading.

* Cache go/analysis facts

Don't load unneeded packages for go/analysis.
Repeated run of go/analysis linters now 10x faster
(2s vs 20s on this repo) than before.
2019-09-17 08:42:16 +03:00

213 lines
5.9 KiB
Go

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"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/logutils"
)
type EnabledSet struct {
m *Manager
v *Validator
log logutils.Log
cfg *config.Config
debugf logutils.DebugFunc
}
func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Config) *EnabledSet {
return &EnabledSet{
m: m,
v: v,
log: log,
cfg: cfg,
debugf: logutils.Debug("enabled_linters"),
}
}
// nolint:gocyclo
func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*linter.Config) map[string]*linter.Config {
resultLintersSet := map[string]*linter.Config{}
switch {
case len(lcfg.Presets) != 0:
break // imply --disable-all
case lcfg.EnableAll:
resultLintersSet = linterConfigsToMap(es.m.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 es.m.GetAllLinterConfigsForPreset(p) {
lc := lc
resultLintersSet[lc.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 es.m.GetLinterConfig(name).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
}
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
}
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
}
resultLintersSet := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
es.verbosePrintLintersStatus(resultLintersSet)
if optimize {
es.optimizeLintersSet(resultLintersSet)
}
es.combineGoAnalysisLinters(resultLintersSet)
var resultLinters []*linter.Config
for _, lc := range resultLintersSet {
resultLinters = append(resultLinters, lc)
}
return resultLinters, nil
}
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)
if !ok {
continue
}
analyzers := lnt.Analyzers()
if len(analyzers) == 0 {
continue // e.g. if "unused" is enabled
}
gl := goanalysis.NewLinter(linter.Name(), "", analyzers, lnt.Cfg())
goanalysisLinters = append(goanalysisLinters, gl)
for _, p := range linter.InPresets {
goanalysisPresets[p] = true
}
for a, name := range lnt.AnalyzerToLinterNameMapping() {
analyzerToLinterName[a] = name
}
}
if len(goanalysisLinters) <= 1 {
es.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters))
return
}
for _, lnt := range goanalysisLinters {
delete(linters, lnt.Name())
}
ml := goanalysis.NewMetaLinter(goanalysisLinters, analyzerToLinterName)
var presets []string
for p := range goanalysisPresets {
presets = append(presets, p)
}
mlConfig := &linter.Config{
Linter: ml,
EnabledByDefault: false,
NeedsSSARepr: false,
InPresets: presets,
Speed: 5,
AlternativeNames: nil,
OriginalURL: "",
ParentLinterName: "",
}
mlConfig = mlConfig.WithLoadForGoAnalysis()
linters[ml.Name()] = mlConfig
es.debugf("Combined %d go/analysis linters into one metalinter", len(goanalysisLinters))
}
func (es EnabledSet) verbosePrintLintersStatus(lcs map[string]*linter.Config) {
var linterNames []string
for _, lc := range lcs {
linterNames = append(linterNames, lc.Name())
}
sort.StringSlice(linterNames).Sort()
es.log.Infof("Active %d linters: %s", len(linterNames), linterNames)
if len(es.cfg.Linters.Presets) != 0 {
sort.StringSlice(es.cfg.Linters.Presets).Sort()
es.log.Infof("Active presets: %s", es.cfg.Linters.Presets)
}
}