github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/lint/runner.go (about)

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