github.com/shulhan/golangci-lint@v1.10.1/pkg/lint/runner.go (about)

     1  package lint
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime/debug"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/golangci/golangci-lint/pkg/config"
    13  	"github.com/golangci/golangci-lint/pkg/lint/astcache"
    14  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    15  	"github.com/golangci/golangci-lint/pkg/logutils"
    16  	"github.com/golangci/golangci-lint/pkg/result"
    17  	"github.com/golangci/golangci-lint/pkg/result/processors"
    18  	"github.com/golangci/golangci-lint/pkg/timeutils"
    19  )
    20  
    21  type Runner struct {
    22  	Processors []processors.Processor
    23  	Log        logutils.Log
    24  }
    25  
    26  func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log) (*Runner, error) {
    27  	icfg := cfg.Issues
    28  	excludePatterns := icfg.ExcludePatterns
    29  	if icfg.UseDefaultExcludes {
    30  		excludePatterns = append(excludePatterns, config.GetDefaultExcludePatternsStrings()...)
    31  	}
    32  
    33  	var excludeTotalPattern string
    34  	if len(excludePatterns) != 0 {
    35  		excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|"))
    36  	}
    37  
    38  	skipFilesProcessor, err := processors.NewSkipFiles(cfg.Run.SkipFiles)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	return &Runner{
    44  		Processors: []processors.Processor{
    45  			processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
    46  			processors.NewCgo(),
    47  			skipFilesProcessor,
    48  
    49  			processors.NewAutogeneratedExclude(astCache),
    50  			processors.NewExclude(excludeTotalPattern),
    51  			processors.NewNolint(astCache),
    52  
    53  			processors.NewUniqByLine(),
    54  			processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
    55  			processors.NewMaxPerFileFromLinter(),
    56  			processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues")),
    57  			processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter")),
    58  			processors.NewSourceCode(log.Child("source_code")),
    59  		},
    60  		Log: log,
    61  	}, nil
    62  }
    63  
    64  type lintRes struct {
    65  	linter linter.Config
    66  	err    error
    67  	issues []result.Issue
    68  }
    69  
    70  func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context,
    71  	lc linter.Config) (ret []result.Issue, err error) {
    72  
    73  	defer func() {
    74  		if panicData := recover(); panicData != nil {
    75  			err = fmt.Errorf("panic occurred: %s", panicData)
    76  			r.Log.Warnf("Panic stack trace: %s", debug.Stack())
    77  		}
    78  	}()
    79  
    80  	specificLintCtx := *lintCtx
    81  	specificLintCtx.Log = r.Log.Child(lc.Linter.Name())
    82  	issues, err := lc.Linter.Run(ctx, &specificLintCtx)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	for _, i := range issues {
    88  		i.FromLinter = lc.Linter.Name()
    89  	}
    90  
    91  	return issues, nil
    92  }
    93  
    94  func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context,
    95  	tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) {
    96  
    97  	sw := timeutils.NewStopwatch(name, r.Log)
    98  	defer sw.Print()
    99  
   100  	for {
   101  		select {
   102  		case <-ctx.Done():
   103  			return
   104  		case lc, ok := <-tasksCh:
   105  			if !ok {
   106  				return
   107  			}
   108  			if ctx.Err() != nil {
   109  				// XXX: if check it in only int a select
   110  				// it's possible to not enter to this case until tasksCh is empty.
   111  				return
   112  			}
   113  			var issues []result.Issue
   114  			var err error
   115  			sw.TrackStage(lc.Linter.Name(), func() {
   116  				issues, err = r.runLinterSafe(ctx, lintCtx, lc)
   117  			})
   118  			lintResultsCh <- lintRes{
   119  				linter: lc,
   120  				err:    err,
   121  				issues: issues,
   122  			}
   123  		}
   124  	}
   125  }
   126  
   127  func (r Runner) logWorkersStat(workersFinishTimes []time.Time) {
   128  	lastFinishTime := workersFinishTimes[0]
   129  	for _, t := range workersFinishTimes {
   130  		if t.After(lastFinishTime) {
   131  			lastFinishTime = t
   132  		}
   133  	}
   134  
   135  	logStrings := []string{}
   136  	for i, t := range workersFinishTimes {
   137  		if t.Equal(lastFinishTime) {
   138  			continue
   139  		}
   140  
   141  		logStrings = append(logStrings, fmt.Sprintf("#%d: %s", i+1, lastFinishTime.Sub(t)))
   142  	}
   143  
   144  	r.Log.Infof("Workers idle times: %s", strings.Join(logStrings, ", "))
   145  }
   146  
   147  func getSortedLintersConfigs(linters []linter.Config) []linter.Config {
   148  	ret := make([]linter.Config, len(linters))
   149  	copy(ret, linters)
   150  
   151  	sort.Slice(ret, func(i, j int) bool {
   152  		return ret[i].GetSpeed() < ret[j].GetSpeed()
   153  	})
   154  
   155  	return ret
   156  }
   157  
   158  func (r *Runner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []linter.Config) <-chan lintRes {
   159  	tasksCh := make(chan linter.Config, len(linters))
   160  	lintResultsCh := make(chan lintRes, len(linters))
   161  	var wg sync.WaitGroup
   162  
   163  	workersFinishTimes := make([]time.Time, lintCtx.Cfg.Run.Concurrency)
   164  
   165  	for i := 0; i < lintCtx.Cfg.Run.Concurrency; i++ {
   166  		wg.Add(1)
   167  		go func(i int) {
   168  			defer wg.Done()
   169  			name := fmt.Sprintf("worker.%d", i+1)
   170  			r.runWorker(ctx, lintCtx, tasksCh, lintResultsCh, name)
   171  			workersFinishTimes[i] = time.Now()
   172  		}(i)
   173  	}
   174  
   175  	lcs := getSortedLintersConfigs(linters)
   176  	for _, lc := range lcs {
   177  		tasksCh <- lc
   178  	}
   179  	close(tasksCh)
   180  
   181  	go func() {
   182  		wg.Wait()
   183  		close(lintResultsCh)
   184  
   185  		r.logWorkersStat(workersFinishTimes)
   186  	}()
   187  
   188  	return lintResultsCh
   189  }
   190  
   191  func (r Runner) processLintResults(inCh <-chan lintRes) <-chan lintRes {
   192  	outCh := make(chan lintRes, 64)
   193  
   194  	go func() {
   195  		sw := timeutils.NewStopwatch("processing", r.Log)
   196  
   197  		defer close(outCh)
   198  
   199  		for res := range inCh {
   200  			if res.err != nil {
   201  				r.Log.Warnf("Can't run linter %s: %s", res.linter.Linter.Name(), res.err)
   202  				continue
   203  			}
   204  
   205  			if len(res.issues) != 0 {
   206  				res.issues = r.processIssues(res.issues, sw)
   207  				outCh <- res
   208  			}
   209  		}
   210  
   211  		// finalize processors: logging, clearing, no heavy work here
   212  
   213  		for _, p := range r.Processors {
   214  			sw.TrackStage(p.Name(), func() {
   215  				p.Finish()
   216  			})
   217  		}
   218  
   219  		sw.PrintStages()
   220  	}()
   221  
   222  	return outCh
   223  }
   224  
   225  func collectIssues(resCh <-chan lintRes) <-chan result.Issue {
   226  	retIssues := make(chan result.Issue, 1024)
   227  	go func() {
   228  		defer close(retIssues)
   229  
   230  		for res := range resCh {
   231  			if len(res.issues) == 0 {
   232  				continue
   233  			}
   234  
   235  			for _, i := range res.issues {
   236  				retIssues <- i
   237  			}
   238  		}
   239  	}()
   240  
   241  	return retIssues
   242  }
   243  
   244  func (r Runner) Run(ctx context.Context, linters []linter.Config, lintCtx *linter.Context) <-chan result.Issue {
   245  	lintResultsCh := r.runWorkers(ctx, lintCtx, linters)
   246  	processedLintResultsCh := r.processLintResults(lintResultsCh)
   247  	if ctx.Err() != nil {
   248  		// XXX: always process issues, even if timeout occurred
   249  		finishedLintersN := 0
   250  		for range processedLintResultsCh {
   251  			finishedLintersN++
   252  		}
   253  
   254  		r.Log.Errorf("%d/%d linters finished: deadline exceeded: try increase it by passing --deadline option",
   255  			finishedLintersN, len(linters))
   256  	}
   257  
   258  	return collectIssues(processedLintResultsCh)
   259  }
   260  
   261  func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch) []result.Issue {
   262  	for _, p := range r.Processors {
   263  		var newIssues []result.Issue
   264  		var err error
   265  		sw.TrackStage(p.Name(), func() {
   266  			newIssues, err = p.Process(issues)
   267  		})
   268  
   269  		if err != nil {
   270  			r.Log.Warnf("Can't process result by %s processor: %s", p.Name(), err)
   271  		} else {
   272  			issues = newIssues
   273  		}
   274  
   275  		if issues == nil {
   276  			issues = []result.Issue{}
   277  		}
   278  	}
   279  
   280  	return issues
   281  }