golangci-lint/pkg/golinters/megacheck.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

301 lines
7.2 KiB
Go

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 {
var ret []*analysis.Analyzer
for _, v := range m {
ret = append(ret, v)
}
return ret
}
func setGoVersion(analyzers []*analysis.Analyzer) {
const goVersion = 13 // TODO
for _, a := range analyzers {
if v := a.Flags.Lookup("go"); v != nil {
if err := v.Value.Set(fmt.Sprintf("1.%d", goVersion)); err != nil {
debugf("Failed to set go version: %s", err)
}
}
}
}
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
}