github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/lint/runner.go (about) 1 package lint 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "runtime/debug" 8 "strings" 9 10 gopackages "golang.org/x/tools/go/packages" 11 12 "github.com/vanstinator/golangci-lint/internal/errorutil" 13 "github.com/vanstinator/golangci-lint/pkg/config" 14 "github.com/vanstinator/golangci-lint/pkg/fsutils" 15 "github.com/vanstinator/golangci-lint/pkg/goutil" 16 "github.com/vanstinator/golangci-lint/pkg/lint/linter" 17 "github.com/vanstinator/golangci-lint/pkg/lint/lintersdb" 18 "github.com/vanstinator/golangci-lint/pkg/logutils" 19 "github.com/vanstinator/golangci-lint/pkg/packages" 20 "github.com/vanstinator/golangci-lint/pkg/result" 21 "github.com/vanstinator/golangci-lint/pkg/result/processors" 22 "github.com/vanstinator/golangci-lint/pkg/timeutils" 23 ) 24 25 type Runner struct { 26 Processors []processors.Processor 27 Log logutils.Log 28 } 29 30 func NewRunner(cfg *config.Config, log logutils.Log, goenv *goutil.Env, 31 es *lintersdb.EnabledSet, 32 lineCache *fsutils.LineCache, fileCache *fsutils.FileCache, 33 dbManager *lintersdb.Manager, pkgs []*gopackages.Package) (*Runner, error) { 34 // Beware that some processors need to add the path prefix when working with paths 35 // because they get invoked before the path prefixer (exclude and severity rules) 36 // or process other paths (skip files). 37 files := fsutils.NewFiles(lineCache, cfg.Output.PathPrefix) 38 39 skipFilesProcessor, err := processors.NewSkipFiles(cfg.Run.SkipFiles, cfg.Output.PathPrefix) 40 if err != nil { 41 return nil, err 42 } 43 44 skipDirs := cfg.Run.SkipDirs 45 if cfg.Run.UseDefaultSkipDirs { 46 skipDirs = append(skipDirs, packages.StdExcludeDirRegexps...) 47 } 48 skipDirsProcessor, err := processors.NewSkipDirs(skipDirs, log.Child(logutils.DebugKeySkipDirs), cfg.Run.Args, cfg.Output.PathPrefix) 49 if err != nil { 50 return nil, err 51 } 52 53 enabledLinters, err := es.GetEnabledLintersMap() 54 if err != nil { 55 return nil, fmt.Errorf("failed to get enabled linters: %w", err) 56 } 57 58 // print deprecated messages 59 if !cfg.InternalCmdTest { 60 for name, lc := range enabledLinters { 61 if !lc.IsDeprecated() { 62 continue 63 } 64 65 var extra string 66 if lc.Deprecation.Replacement != "" { 67 extra = fmt.Sprintf("Replaced by %s.", lc.Deprecation.Replacement) 68 } 69 70 log.Warnf("The linter '%s' is deprecated (since %s) due to: %s %s", name, lc.Deprecation.Since, lc.Deprecation.Message, extra) 71 } 72 } 73 74 return &Runner{ 75 Processors: []processors.Processor{ 76 processors.NewCgo(goenv), 77 78 // Must go after Cgo. 79 processors.NewFilenameUnadjuster(pkgs, log.Child(logutils.DebugKeyFilenameUnadjuster)), 80 81 // Must be before diff, nolint and exclude autogenerated processor at least. 82 processors.NewPathPrettifier(), 83 skipFilesProcessor, 84 skipDirsProcessor, // must be after path prettifier 85 86 processors.NewAutogeneratedExclude(), 87 88 // Must be before exclude because users see already marked output and configure excluding by it. 89 processors.NewIdentifierMarker(), 90 91 getExcludeProcessor(&cfg.Issues), 92 getExcludeRulesProcessor(&cfg.Issues, log, files), 93 processors.NewNolint(log.Child(logutils.DebugKeyNolint), dbManager, enabledLinters), 94 95 processors.NewUniqByLine(cfg), 96 processors.NewDiff(cfg.Issues.Diff, cfg.Issues.DiffFromRevision, cfg.Issues.DiffPatchFilePath, cfg.Issues.WholeFiles), 97 processors.NewMaxPerFileFromLinter(cfg), 98 processors.NewMaxSameIssues(cfg.Issues.MaxSameIssues, log.Child(logutils.DebugKeyMaxSameIssues), cfg), 99 processors.NewMaxFromLinter(cfg.Issues.MaxIssuesPerLinter, log.Child(logutils.DebugKeyMaxFromLinter), cfg), 100 processors.NewSourceCode(lineCache, log.Child(logutils.DebugKeySourceCode)), 101 processors.NewPathShortener(), 102 getSeverityRulesProcessor(&cfg.Severity, log, files), 103 104 // The fixer still needs to see paths for the issues that are relative to the current directory. 105 processors.NewFixer(cfg, log, fileCache), 106 107 // Now we can modify the issues for output. 108 processors.NewPathPrefixer(cfg.Output.PathPrefix), 109 processors.NewSortResults(cfg), 110 }, 111 Log: log, 112 }, nil 113 } 114 115 func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, 116 lc *linter.Config) (ret []result.Issue, err error) { 117 defer func() { 118 if panicData := recover(); panicData != nil { 119 if pe, ok := panicData.(*errorutil.PanicError); ok { 120 err = fmt.Errorf("%s: %w", lc.Name(), pe) 121 122 // Don't print stacktrace from goroutines twice 123 r.Log.Errorf("Panic: %s: %s", pe, pe.Stack()) 124 } else { 125 err = fmt.Errorf("panic occurred: %s", panicData) 126 r.Log.Errorf("Panic stack trace: %s", debug.Stack()) 127 } 128 } 129 }() 130 131 issues, err := lc.Linter.Run(ctx, lintCtx) 132 133 if lc.DoesChangeTypes { 134 // Packages in lintCtx might be dirty due to the last analysis, 135 // which affects to the next analysis. 136 // To avoid this issue, we clear type information from the packages. 137 // See https://github.com/vanstinator/golangci-lint/pull/944. 138 // Currently, DoesChangeTypes is true only for `unused`. 139 lintCtx.ClearTypesInPackages() 140 } 141 142 if err != nil { 143 return nil, err 144 } 145 146 for i := range issues { 147 if issues[i].FromLinter == "" { 148 issues[i].FromLinter = lc.Name() 149 } 150 } 151 152 return issues, nil 153 } 154 155 type processorStat struct { 156 inCount int 157 outCount int 158 } 159 160 func (r Runner) processLintResults(inIssues []result.Issue) []result.Issue { 161 sw := timeutils.NewStopwatch("processing", r.Log) 162 163 var issuesBefore, issuesAfter int 164 statPerProcessor := map[string]processorStat{} 165 166 var outIssues []result.Issue 167 if len(inIssues) != 0 { 168 issuesBefore += len(inIssues) 169 outIssues = r.processIssues(inIssues, sw, statPerProcessor) 170 issuesAfter += len(outIssues) 171 } 172 173 // finalize processors: logging, clearing, no heavy work here 174 175 for _, p := range r.Processors { 176 p := p 177 sw.TrackStage(p.Name(), func() { 178 p.Finish() 179 }) 180 } 181 182 if issuesBefore != issuesAfter { 183 r.Log.Infof("Issues before processing: %d, after processing: %d", issuesBefore, issuesAfter) 184 } 185 r.printPerProcessorStat(statPerProcessor) 186 sw.PrintStages() 187 188 return outIssues 189 } 190 191 func (r Runner) printPerProcessorStat(stat map[string]processorStat) { 192 parts := make([]string, 0, len(stat)) 193 for name, ps := range stat { 194 if ps.inCount != 0 { 195 parts = append(parts, fmt.Sprintf("%s: %d/%d", name, ps.outCount, ps.inCount)) 196 } 197 } 198 if len(parts) != 0 { 199 r.Log.Infof("Processors filtering stat (out/in): %s", strings.Join(parts, ", ")) 200 } 201 } 202 203 func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *linter.Context) ([]result.Issue, error) { 204 sw := timeutils.NewStopwatch("linters", r.Log) 205 defer sw.Print() 206 207 var ( 208 lintErrors error 209 issues []result.Issue 210 ) 211 212 for _, lc := range linters { 213 lc := lc 214 sw.TrackStage(lc.Name(), func() { 215 linterIssues, err := r.runLinterSafe(ctx, lintCtx, lc) 216 if err != nil { 217 lintErrors = errors.Join(lintErrors, fmt.Errorf("can't run linter %s", lc.Linter.Name()), err) 218 r.Log.Warnf("Can't run linter %s: %v", lc.Linter.Name(), err) 219 220 return 221 } 222 223 issues = append(issues, linterIssues...) 224 }) 225 } 226 227 return r.processLintResults(issues), lintErrors 228 } 229 230 func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, statPerProcessor map[string]processorStat) []result.Issue { 231 for _, p := range r.Processors { 232 var newIssues []result.Issue 233 var err error 234 p := p 235 sw.TrackStage(p.Name(), func() { 236 newIssues, err = p.Process(issues) 237 }) 238 239 if err != nil { 240 r.Log.Warnf("Can't process result by %s processor: %s", p.Name(), err) 241 } else { 242 stat := statPerProcessor[p.Name()] 243 stat.inCount += len(issues) 244 stat.outCount += len(newIssues) 245 statPerProcessor[p.Name()] = stat 246 issues = newIssues 247 } 248 249 if issues == nil { 250 issues = []result.Issue{} 251 } 252 } 253 254 return issues 255 } 256 257 func getExcludeProcessor(cfg *config.Issues) processors.Processor { 258 var excludeTotalPattern string 259 260 if len(cfg.ExcludePatterns) != 0 { 261 excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(cfg.ExcludePatterns, "|")) 262 } 263 264 var excludeProcessor processors.Processor 265 if cfg.ExcludeCaseSensitive { 266 excludeProcessor = processors.NewExcludeCaseSensitive(excludeTotalPattern) 267 } else { 268 excludeProcessor = processors.NewExclude(excludeTotalPattern) 269 } 270 271 return excludeProcessor 272 } 273 274 func getExcludeRulesProcessor(cfg *config.Issues, log logutils.Log, files *fsutils.Files) processors.Processor { 275 var excludeRules []processors.ExcludeRule 276 for _, r := range cfg.ExcludeRules { 277 excludeRules = append(excludeRules, processors.ExcludeRule{ 278 BaseRule: processors.BaseRule{ 279 Text: r.Text, 280 Source: r.Source, 281 Path: r.Path, 282 PathExcept: r.PathExcept, 283 Linters: r.Linters, 284 }, 285 }) 286 } 287 288 if cfg.UseDefaultExcludes { 289 for _, r := range config.GetExcludePatterns(cfg.IncludeDefaultExcludes) { 290 excludeRules = append(excludeRules, processors.ExcludeRule{ 291 BaseRule: processors.BaseRule{ 292 Text: r.Pattern, 293 Linters: []string{r.Linter}, 294 }, 295 }) 296 } 297 } 298 299 var excludeRulesProcessor processors.Processor 300 if cfg.ExcludeCaseSensitive { 301 excludeRulesProcessor = processors.NewExcludeRulesCaseSensitive( 302 excludeRules, 303 files, 304 log.Child(logutils.DebugKeyExcludeRules), 305 ) 306 } else { 307 excludeRulesProcessor = processors.NewExcludeRules( 308 excludeRules, 309 files, 310 log.Child(logutils.DebugKeyExcludeRules), 311 ) 312 } 313 314 return excludeRulesProcessor 315 } 316 317 func getSeverityRulesProcessor(cfg *config.Severity, log logutils.Log, files *fsutils.Files) processors.Processor { 318 var severityRules []processors.SeverityRule 319 for _, r := range cfg.Rules { 320 severityRules = append(severityRules, processors.SeverityRule{ 321 Severity: r.Severity, 322 BaseRule: processors.BaseRule{ 323 Text: r.Text, 324 Source: r.Source, 325 Path: r.Path, 326 PathExcept: r.PathExcept, 327 Linters: r.Linters, 328 }, 329 }) 330 } 331 332 var severityRulesProcessor processors.Processor 333 if cfg.CaseSensitive { 334 severityRulesProcessor = processors.NewSeverityRulesCaseSensitive( 335 cfg.Default, 336 severityRules, 337 files, 338 log.Child(logutils.DebugKeySeverityRules), 339 ) 340 } else { 341 severityRulesProcessor = processors.NewSeverityRules( 342 cfg.Default, 343 severityRules, 344 files, 345 log.Child(logutils.DebugKeySeverityRules), 346 ) 347 } 348 349 return severityRulesProcessor 350 }