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