diff --git a/pkg/golinters/makezero.go b/pkg/golinters/makezero.go index 7b7ec09b9ec0..cdde09291d22 100644 --- a/pkg/golinters/makezero.go +++ b/pkg/golinters/makezero.go @@ -56,5 +56,5 @@ func NewMakezero() *goanalysis.Linter { } }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax | goanalysis.LoadModeTypesInfo) + }).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/pkg/golinters/typecheck.go b/pkg/golinters/typecheck.go index 436530a8dbe6..cce9d50d9c17 100644 --- a/pkg/golinters/typecheck.go +++ b/pkg/golinters/typecheck.go @@ -8,6 +8,7 @@ import ( func NewTypecheck() *goanalysis.Linter { const linterName = "typecheck" + analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, @@ -15,12 +16,17 @@ func NewTypecheck() *goanalysis.Linter { return nil, nil }, } + + // Note: typecheck doesn't require the LoadModeWholeProgram + // but it's a hack to force this linter to be the first linter in all the cases. 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) + ).WithLoadMode(goanalysis.LoadModeWholeProgram) + linter.SetTypecheckMode() + return linter } diff --git a/pkg/golinters/unused.go b/pkg/golinters/unused.go index 6998ebde053f..3a6dcdbce9d5 100644 --- a/pkg/golinters/unused.go +++ b/pkg/golinters/unused.go @@ -59,7 +59,7 @@ func NewUnused() *goanalysis.Linter { nil, ).WithIssuesReporter(func(lintCtx *linter.Context) []goanalysis.Issue { return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax | goanalysis.LoadModeTypesInfo) + }).WithLoadMode(goanalysis.LoadModeWholeProgram) return lnt } diff --git a/pkg/lint/linter/config.go b/pkg/lint/linter/config.go index 0bc664732c01..22f22a10d2bb 100644 --- a/pkg/lint/linter/config.go +++ b/pkg/lint/linter/config.go @@ -20,6 +20,14 @@ const ( PresetUnused = "unused" // Related to the detection of unused code. ) +const ( + // typecheck must be first because it checks the compiling errors. + FirstLinter = "typecheck" + + // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives. + LastLinter = "nolintlint" +) + type Deprecation struct { Since string Message string diff --git a/pkg/lint/lintersdb/enabled_set.go b/pkg/lint/lintersdb/enabled_set.go index eced95f6552d..66e553cef9aa 100644 --- a/pkg/lint/lintersdb/enabled_set.go +++ b/pkg/lint/lintersdb/enabled_set.go @@ -111,6 +111,15 @@ func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) { // Make order of execution of linters (go/analysis metalinter and unused) stable. sort.Slice(resultLinters, func(i, j int) bool { a, b := resultLinters[i], resultLinters[j] + + if a.Name() == linter.FirstLinter || b.Name() == linter.LastLinter { + return true + } + + if a.Name() == linter.LastLinter || b.Name() == linter.FirstLinter { + return false + } + if a.DoesChangeTypes != b.DoesChangeTypes { return b.DoesChangeTypes // move type-changing linters to the end to optimize speed } @@ -149,8 +158,19 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) // Make order of execution of go/analysis analyzers stable. sort.Slice(goanalysisLinters, func(i, j int) bool { - return strings.Compare(goanalysisLinters[i].Name(), goanalysisLinters[j].Name()) <= 0 + a, b := goanalysisLinters[i], goanalysisLinters[j] + + if a.Name() == linter.FirstLinter || b.Name() == linter.LastLinter { + return true + } + + if a.Name() == linter.LastLinter || b.Name() == linter.FirstLinter { + return false + } + + return strings.Compare(a.Name(), b.Name()) <= 0 }) + ml := goanalysis.NewMetaLinter(goanalysisLinters) var presets []string diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 9938ed4f889a..f2a3b3c7e114 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -131,6 +131,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { const megacheckName = "megacheck" lcs := []*linter.Config{ + linter.NewConfig(golinters.NewTypecheck()). + WithSince("v1.3.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL(""), + linter.NewConfig(golinters.NewGovet(govetCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). @@ -247,11 +253,6 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.20.0"). WithPresets(linter.PresetComplexity). WithURL("https://github.com/uudashr/gocognit"), - linter.NewConfig(golinters.NewTypecheck()). - WithSince("v1.3.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs). - WithURL(""), linter.NewConfig(golinters.NewAsciicheck()). WithSince("v1.26.0"). WithPresets(linter.PresetBugs, linter.PresetStyle). diff --git a/pkg/lint/runner.go b/pkg/lint/runner.go index bf4382a7862c..bf35bb1d84ac 100644 --- a/pkg/lint/runner.go +++ b/pkg/lint/runner.go @@ -200,7 +200,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint 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) + r.Log.Warnf("Can't run linter %s: %v", lc.Linter.Name(), err) if os.Getenv("GOLANGCI_COM_RUN") == "" { // Don't stop all linters on one linter failure for golangci.com. runErr = err @@ -209,6 +209,10 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint } issues = append(issues, linterIssues...) }) + + if lc.Name() == linter.FirstLinter && len(issues) > 0 { + return r.processLintResults(issues), nil + } } return r.processLintResults(issues), runErr