Isaev Denis 95ec0cf21e
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
2019-09-30 16:19:41 +03:00

136 lines
3.1 KiB
Go

package goanalysis
import (
"context"
"github.com/pkg/errors"
"golang.org/x/tools/go/analysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)
type MetaLinter struct {
linters []*Linter
}
func NewMetaLinter(linters []*Linter) *MetaLinter {
return &MetaLinter{linters: linters}
}
func (ml MetaLinter) Name() string {
return "goanalysis_metalinter"
}
func (ml MetaLinter) Desc() string {
return ""
}
func (ml MetaLinter) isTypecheckMode() bool {
for _, linter := range ml.linters {
if linter.isTypecheckMode {
return true
}
}
return false
}
func (ml MetaLinter) getLoadMode() LoadMode {
loadMode := LoadModeNone
for _, linter := range ml.linters {
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
}
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
}
// Don't print all errs: they can duplicate.
if len(errs) != 0 {
return nil, errs[0]
}
return buildAllIssues(), nil
}