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  }