.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.1.1 / lintcmd / cmd.go
1 package lintcmd
2
3 import (
4         "crypto/sha256"
5         "flag"
6         "fmt"
7         "go/build"
8         "go/token"
9         "io"
10         "log"
11         "os"
12         "os/signal"
13         "path/filepath"
14         "regexp"
15         "runtime"
16         "runtime/pprof"
17         "runtime/trace"
18         "sort"
19         "strconv"
20         "strings"
21         "sync"
22         "time"
23         "unicode"
24
25         "honnef.co/go/tools/analysis/lint"
26         "honnef.co/go/tools/config"
27         "honnef.co/go/tools/go/loader"
28         "honnef.co/go/tools/internal/cache"
29         "honnef.co/go/tools/lintcmd/runner"
30         "honnef.co/go/tools/lintcmd/version"
31         "honnef.co/go/tools/unused"
32
33         "golang.org/x/tools/go/analysis"
34         "golang.org/x/tools/go/buildutil"
35         "golang.org/x/tools/go/packages"
36 )
37
38 type ignore interface {
39         Match(p problem) bool
40 }
41
42 type lineIgnore struct {
43         File    string
44         Line    int
45         Checks  []string
46         Matched bool
47         Pos     token.Position
48 }
49
50 func (li *lineIgnore) Match(p problem) bool {
51         pos := p.Position
52         if pos.Filename != li.File || pos.Line != li.Line {
53                 return false
54         }
55         for _, c := range li.Checks {
56                 if m, _ := filepath.Match(c, p.Category); m {
57                         li.Matched = true
58                         return true
59                 }
60         }
61         return false
62 }
63
64 func (li *lineIgnore) String() string {
65         matched := "not matched"
66         if li.Matched {
67                 matched = "matched"
68         }
69         return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
70 }
71
72 type fileIgnore struct {
73         File   string
74         Checks []string
75 }
76
77 func (fi *fileIgnore) Match(p problem) bool {
78         if p.Position.Filename != fi.File {
79                 return false
80         }
81         for _, c := range fi.Checks {
82                 if m, _ := filepath.Match(c, p.Category); m {
83                         return true
84                 }
85         }
86         return false
87 }
88
89 type severity uint8
90
91 const (
92         severityError severity = iota
93         severityWarning
94         severityIgnored
95 )
96
97 func (s severity) String() string {
98         switch s {
99         case severityError:
100                 return "error"
101         case severityWarning:
102                 return "warning"
103         case severityIgnored:
104                 return "ignored"
105         default:
106                 return fmt.Sprintf("Severity(%d)", s)
107         }
108 }
109
110 // problem represents a problem in some source code.
111 type problem struct {
112         runner.Diagnostic
113         Severity severity
114 }
115
116 func (p problem) equal(o problem) bool {
117         return p.Position == o.Position &&
118                 p.End == o.End &&
119                 p.Message == o.Message &&
120                 p.Category == o.Category &&
121                 p.Severity == o.Severity
122 }
123
124 func (p *problem) String() string {
125         return fmt.Sprintf("%s (%s)", p.Message, p.Category)
126 }
127
128 // A linter lints Go source code.
129 type linter struct {
130         Checkers []*analysis.Analyzer
131         Config   config.Config
132         Runner   *runner.Runner
133 }
134
135 func failed(res runner.Result) []problem {
136         var problems []problem
137
138         for _, e := range res.Errors {
139                 switch e := e.(type) {
140                 case packages.Error:
141                         msg := e.Msg
142                         if len(msg) != 0 && msg[0] == '\n' {
143                                 // TODO(dh): See https://github.com/golang/go/issues/32363
144                                 msg = msg[1:]
145                         }
146
147                         var posn token.Position
148                         if e.Pos == "" {
149                                 // Under certain conditions (malformed package
150                                 // declarations, multiple packages in the same
151                                 // directory), go list emits an error on stderr
152                                 // instead of JSON. Those errors do not have
153                                 // associated position information in
154                                 // go/packages.Error, even though the output on
155                                 // stderr may contain it.
156                                 if p, n, err := parsePos(msg); err == nil {
157                                         if abs, err := filepath.Abs(p.Filename); err == nil {
158                                                 p.Filename = abs
159                                         }
160                                         posn = p
161                                         msg = msg[n+2:]
162                                 }
163                         } else {
164                                 var err error
165                                 posn, _, err = parsePos(e.Pos)
166                                 if err != nil {
167                                         panic(fmt.Sprintf("internal error: %s", e))
168                                 }
169                         }
170                         p := problem{
171                                 Diagnostic: runner.Diagnostic{
172                                         Position: posn,
173                                         Message:  msg,
174                                         Category: "compile",
175                                 },
176                                 Severity: severityError,
177                         }
178                         problems = append(problems, p)
179                 case error:
180                         p := problem{
181                                 Diagnostic: runner.Diagnostic{
182                                         Position: token.Position{},
183                                         Message:  e.Error(),
184                                         Category: "compile",
185                                 },
186                                 Severity: severityError,
187                         }
188                         problems = append(problems, p)
189                 }
190         }
191
192         return problems
193 }
194
195 type unusedKey struct {
196         pkgPath string
197         base    string
198         line    int
199         name    string
200 }
201
202 type unusedPair struct {
203         key unusedKey
204         obj unused.SerializedObject
205 }
206
207 func success(allowedChecks map[string]bool, res runner.ResultData) []problem {
208         diags := res.Diagnostics
209         var problems []problem
210         for _, diag := range diags {
211                 if !allowedChecks[diag.Category] {
212                         continue
213                 }
214                 problems = append(problems, problem{Diagnostic: diag})
215         }
216         return problems
217 }
218
219 func filterIgnored(problems []problem, res runner.ResultData, allowedAnalyzers map[string]bool) ([]problem, error) {
220         couldveMatched := func(ig *lineIgnore) bool {
221                 for _, c := range ig.Checks {
222                         if c == "U1000" {
223                                 // We never want to flag ignores for U1000,
224                                 // because U1000 isn't local to a single
225                                 // package. For example, an identifier may
226                                 // only be used by tests, in which case an
227                                 // ignore would only fire when not analyzing
228                                 // tests. To avoid spurious "useless ignore"
229                                 // warnings, just never flag U1000.
230                                 return false
231                         }
232
233                         // Even though the runner always runs all analyzers, we
234                         // still only flag unmatched ignores for the set of
235                         // analyzers the user has expressed interest in. That way,
236                         // `staticcheck -checks=SA1000` won't complain about an
237                         // unmatched ignore for an unrelated check.
238                         if allowedAnalyzers[c] {
239                                 return true
240                         }
241                 }
242
243                 return false
244         }
245
246         ignores, moreProblems := parseDirectives(res.Directives)
247
248         for _, ig := range ignores {
249                 for i := range problems {
250                         p := &problems[i]
251                         if ig.Match(*p) {
252                                 p.Severity = severityIgnored
253                         }
254                 }
255
256                 if ig, ok := ig.(*lineIgnore); ok && !ig.Matched && couldveMatched(ig) {
257                         p := problem{
258                                 Diagnostic: runner.Diagnostic{
259                                         Position: ig.Pos,
260                                         Message:  "this linter directive didn't match anything; should it be removed?",
261                                         Category: "staticcheck",
262                                 },
263                         }
264                         moreProblems = append(moreProblems, p)
265                 }
266         }
267
268         return append(problems, moreProblems...), nil
269 }
270
271 func newLinter(cfg config.Config) (*linter, error) {
272         r, err := runner.New(cfg)
273         if err != nil {
274                 return nil, err
275         }
276         return &linter{
277                 Config: cfg,
278                 Runner: r,
279         }, nil
280 }
281
282 func (l *linter) SetGoVersion(n int) {
283         l.Runner.GoVersion = n
284 }
285
286 func (l *linter) Lint(cfg *packages.Config, patterns []string) (problems []problem, warnings []string, err error) {
287         results, err := l.Runner.Run(cfg, l.Checkers, patterns)
288         if err != nil {
289                 return nil, nil, err
290         }
291
292         if len(results) == 0 && err == nil {
293                 // TODO(dh): emulate Go's behavior more closely once we have
294                 // access to go list's Match field.
295                 for _, pattern := range patterns {
296                         fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
297                 }
298         }
299
300         analyzerNames := make([]string, len(l.Checkers))
301         for i, a := range l.Checkers {
302                 analyzerNames[i] = a.Name
303         }
304
305         used := map[unusedKey]bool{}
306         var unuseds []unusedPair
307         for _, res := range results {
308                 if len(res.Errors) > 0 && !res.Failed {
309                         panic("package has errors but isn't marked as failed")
310                 }
311                 if res.Failed {
312                         problems = append(problems, failed(res)...)
313                 } else {
314                         if res.Skipped {
315                                 warnings = append(warnings, fmt.Sprintf("skipped package %s because it is too large", res.Package))
316                                 continue
317                         }
318
319                         if !res.Initial {
320                                 continue
321                         }
322
323                         allowedAnalyzers := filterAnalyzerNames(analyzerNames, res.Config.Checks)
324                         resd, err := res.Load()
325                         if err != nil {
326                                 return nil, nil, err
327                         }
328                         ps := success(allowedAnalyzers, resd)
329                         filtered, err := filterIgnored(ps, resd, allowedAnalyzers)
330                         if err != nil {
331                                 return nil, nil, err
332                         }
333                         problems = append(problems, filtered...)
334
335                         for _, obj := range resd.Unused.Used {
336                                 // FIXME(dh): pick the object whose filename does not include $GOROOT
337                                 key := unusedKey{
338                                         pkgPath: res.Package.PkgPath,
339                                         base:    filepath.Base(obj.Position.Filename),
340                                         line:    obj.Position.Line,
341                                         name:    obj.Name,
342                                 }
343                                 used[key] = true
344                         }
345
346                         if allowedAnalyzers["U1000"] {
347                                 for _, obj := range resd.Unused.Unused {
348                                         key := unusedKey{
349                                                 pkgPath: res.Package.PkgPath,
350                                                 base:    filepath.Base(obj.Position.Filename),
351                                                 line:    obj.Position.Line,
352                                                 name:    obj.Name,
353                                         }
354                                         unuseds = append(unuseds, unusedPair{key, obj})
355                                         if _, ok := used[key]; !ok {
356                                                 used[key] = false
357                                         }
358                                 }
359                         }
360                 }
361         }
362
363         for _, uo := range unuseds {
364                 if used[uo.key] {
365                         continue
366                 }
367                 if uo.obj.InGenerated {
368                         continue
369                 }
370                 problems = append(problems, problem{
371                         Diagnostic: runner.Diagnostic{
372                                 Position: uo.obj.DisplayPosition,
373                                 Message:  fmt.Sprintf("%s %s is unused", uo.obj.Kind, uo.obj.Name),
374                                 Category: "U1000",
375                         },
376                 })
377         }
378
379         if len(problems) == 0 {
380                 return nil, warnings, nil
381         }
382
383         sort.Slice(problems, func(i, j int) bool {
384                 pi := problems[i].Position
385                 pj := problems[j].Position
386
387                 if pi.Filename != pj.Filename {
388                         return pi.Filename < pj.Filename
389                 }
390                 if pi.Line != pj.Line {
391                         return pi.Line < pj.Line
392                 }
393                 if pi.Column != pj.Column {
394                         return pi.Column < pj.Column
395                 }
396
397                 return problems[i].Message < problems[j].Message
398         })
399
400         var out []problem
401         out = append(out, problems[0])
402         for i, p := range problems[1:] {
403                 // We may encounter duplicate problems because one file
404                 // can be part of many packages.
405                 if !problems[i].equal(p) {
406                         out = append(out, p)
407                 }
408         }
409         return out, warnings, nil
410 }
411
412 func filterAnalyzerNames(analyzers []string, checks []string) map[string]bool {
413         allowedChecks := map[string]bool{}
414
415         for _, check := range checks {
416                 b := true
417                 if len(check) > 1 && check[0] == '-' {
418                         b = false
419                         check = check[1:]
420                 }
421                 if check == "*" || check == "all" {
422                         // Match all
423                         for _, c := range analyzers {
424                                 allowedChecks[c] = b
425                         }
426                 } else if strings.HasSuffix(check, "*") {
427                         // Glob
428                         prefix := check[:len(check)-1]
429                         isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
430
431                         for _, a := range analyzers {
432                                 idx := strings.IndexFunc(a, func(r rune) bool { return unicode.IsNumber(r) })
433                                 if isCat {
434                                         // Glob is S*, which should match S1000 but not SA1000
435                                         cat := a[:idx]
436                                         if prefix == cat {
437                                                 allowedChecks[a] = b
438                                         }
439                                 } else {
440                                         // Glob is S1*
441                                         if strings.HasPrefix(a, prefix) {
442                                                 allowedChecks[a] = b
443                                         }
444                                 }
445                         }
446                 } else {
447                         // Literal check name
448                         allowedChecks[check] = b
449                 }
450         }
451         return allowedChecks
452 }
453
454 var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?`)
455
456 func parsePos(pos string) (token.Position, int, error) {
457         if pos == "-" || pos == "" {
458                 return token.Position{}, 0, nil
459         }
460         parts := posRe.FindStringSubmatch(pos)
461         if parts == nil {
462                 return token.Position{}, 0, fmt.Errorf("internal error: malformed position %q", pos)
463         }
464         file := parts[1]
465         line, _ := strconv.Atoi(parts[2])
466         col, _ := strconv.Atoi(parts[3])
467         return token.Position{
468                 Filename: file,
469                 Line:     line,
470                 Column:   col,
471         }, len(parts[0]), nil
472 }
473
474 func usage(name string, flags *flag.FlagSet) func() {
475         return func() {
476                 fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
477                 fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
478                 fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
479                 fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
480                 fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
481                 fmt.Fprintf(os.Stderr, "Flags:\n")
482                 flags.PrintDefaults()
483         }
484 }
485
486 type list []string
487
488 func (list *list) String() string {
489         return `"` + strings.Join(*list, ",") + `"`
490 }
491
492 func (list *list) Set(s string) error {
493         if s == "" {
494                 *list = nil
495                 return nil
496         }
497
498         *list = strings.Split(s, ",")
499         return nil
500 }
501
502 func FlagSet(name string) *flag.FlagSet {
503         flags := flag.NewFlagSet("", flag.ExitOnError)
504         flags.Usage = usage(name, flags)
505         flags.String("tags", "", "List of `build tags`")
506         flags.Bool("tests", true, "Include tests")
507         flags.Bool("version", false, "Print version and exit")
508         flags.Bool("show-ignored", false, "Don't filter ignored problems")
509         flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
510         flags.String("explain", "", "Print description of `check`")
511
512         flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
513         flags.String("debug.memprofile", "", "Write memory profile to `file`")
514         flags.Bool("debug.version", false, "Print detailed version information about this program")
515         flags.Bool("debug.no-compile-errors", false, "Don't print compile errors")
516         flags.String("debug.measure-analyzers", "", "Write analysis measurements to `file`. `file` will be opened for appending if it already exists.")
517         flags.String("debug.trace", "", "Write trace to `file`")
518
519         checks := list{"inherit"}
520         fail := list{"all"}
521         flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
522         flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
523
524         tags := build.Default.ReleaseTags
525         v := tags[len(tags)-1][2:]
526         version := new(lint.VersionFlag)
527         if err := version.Set(v); err != nil {
528                 panic(fmt.Sprintf("internal error: %s", err))
529         }
530
531         flags.Var(version, "go", "Target Go `version` in the format '1.x'")
532         return flags
533 }
534
535 func findCheck(cs []*analysis.Analyzer, check string) (*analysis.Analyzer, bool) {
536         for _, c := range cs {
537                 if c.Name == check {
538                         return c, true
539                 }
540         }
541         return nil, false
542 }
543
544 func ProcessFlagSet(cs []*analysis.Analyzer, fs *flag.FlagSet) {
545         tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
546         tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
547         goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
548         theFormatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
549         printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
550         showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
551         explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
552
553         cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
554         memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
555         debugVersion := fs.Lookup("debug.version").Value.(flag.Getter).Get().(bool)
556         debugNoCompile := fs.Lookup("debug.no-compile-errors").Value.(flag.Getter).Get().(bool)
557         traceOut := fs.Lookup("debug.trace").Value.(flag.Getter).Get().(string)
558
559         var measureAnalyzers func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration)
560         if path := fs.Lookup("debug.measure-analyzers").Value.(flag.Getter).Get().(string); path != "" {
561                 f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
562                 if err != nil {
563                         log.Fatal(err)
564                 }
565
566                 mu := &sync.Mutex{}
567                 measureAnalyzers = func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) {
568                         mu.Lock()
569                         defer mu.Unlock()
570                         // FIXME(dh): print pkg.ID
571                         if _, err := fmt.Fprintf(f, "%s\t%s\t%d\n", analysis.Name, pkg, d.Nanoseconds()); err != nil {
572                                 log.Println("error writing analysis measurements:", err)
573                         }
574                 }
575         }
576
577         cfg := config.Config{}
578         cfg.Checks = *fs.Lookup("checks").Value.(*list)
579
580         exit := func(code int) {
581                 if cpuProfile != "" {
582                         pprof.StopCPUProfile()
583                 }
584                 if memProfile != "" {
585                         f, err := os.Create(memProfile)
586                         if err != nil {
587                                 panic(err)
588                         }
589                         runtime.GC()
590                         pprof.WriteHeapProfile(f)
591                 }
592                 if traceOut != "" {
593                         trace.Stop()
594                 }
595                 os.Exit(code)
596         }
597         if cpuProfile != "" {
598                 f, err := os.Create(cpuProfile)
599                 if err != nil {
600                         log.Fatal(err)
601                 }
602                 pprof.StartCPUProfile(f)
603         }
604         if traceOut != "" {
605                 f, err := os.Create(traceOut)
606                 if err != nil {
607                         log.Fatal(err)
608                 }
609                 trace.Start(f)
610         }
611
612         if debugVersion {
613                 version.Verbose()
614                 exit(0)
615         }
616
617         if printVersion {
618                 version.Print()
619                 exit(0)
620         }
621
622         // Validate that the tags argument is well-formed. go/packages
623         // doesn't detect malformed build flags and returns unhelpful
624         // errors.
625         tf := buildutil.TagsFlag{}
626         if err := tf.Set(tags); err != nil {
627                 fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", tags, err))
628                 exit(1)
629         }
630
631         if explain != "" {
632                 var haystack []*analysis.Analyzer
633                 haystack = append(haystack, cs...)
634                 check, ok := findCheck(haystack, explain)
635                 if !ok {
636                         fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
637                         exit(1)
638                 }
639                 if check.Doc == "" {
640                         fmt.Fprintln(os.Stderr, explain, "has no documentation")
641                         exit(1)
642                 }
643                 fmt.Println(check.Doc)
644                 exit(0)
645         }
646
647         var f formatter
648         switch theFormatter {
649         case "text":
650                 f = textFormatter{W: os.Stdout}
651         case "stylish":
652                 f = &stylishFormatter{W: os.Stdout}
653         case "json":
654                 f = jsonFormatter{W: os.Stdout}
655         case "null":
656                 f = nullFormatter{}
657         default:
658                 fmt.Fprintf(os.Stderr, "unsupported output format %q\n", theFormatter)
659                 exit(2)
660         }
661
662         ps, warnings, err := doLint(cs, fs.Args(), &options{
663                 Tags:                     tags,
664                 LintTests:                tests,
665                 GoVersion:                goVersion,
666                 Config:                   cfg,
667                 PrintAnalyzerMeasurement: measureAnalyzers,
668         })
669         if err != nil {
670                 fmt.Fprintln(os.Stderr, err)
671                 exit(1)
672         }
673
674         for _, w := range warnings {
675                 fmt.Fprintln(os.Stderr, "warning:", w)
676         }
677
678         var (
679                 numErrors   int
680                 numWarnings int
681                 numIgnored  int
682         )
683
684         fail := *fs.Lookup("fail").Value.(*list)
685         analyzerNames := make([]string, len(cs))
686         for i, a := range cs {
687                 analyzerNames[i] = a.Name
688         }
689         shouldExit := filterAnalyzerNames(analyzerNames, fail)
690         shouldExit["staticcheck"] = true
691         shouldExit["compile"] = true
692
693         for _, p := range ps {
694                 if p.Category == "compile" && debugNoCompile {
695                         continue
696                 }
697                 if p.Severity == severityIgnored && !showIgnored {
698                         numIgnored++
699                         continue
700                 }
701                 if shouldExit[p.Category] {
702                         numErrors++
703                 } else {
704                         p.Severity = severityWarning
705                         numWarnings++
706                 }
707                 f.Format(p)
708         }
709         if f, ok := f.(statter); ok {
710                 f.Stats(len(ps), numErrors, numWarnings, numIgnored)
711         }
712
713         if numErrors > 0 {
714                 exit(1)
715         }
716         exit(0)
717 }
718
719 type options struct {
720         Config config.Config
721
722         Tags                     string
723         LintTests                bool
724         GoVersion                int
725         PrintAnalyzerMeasurement func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration)
726 }
727
728 func computeSalt() ([]byte, error) {
729         if version.Version != "devel" {
730                 return []byte(version.Version), nil
731         }
732         p, err := os.Executable()
733         if err != nil {
734                 return nil, err
735         }
736         f, err := os.Open(p)
737         if err != nil {
738                 return nil, err
739         }
740         defer f.Close()
741         h := sha256.New()
742         if _, err := io.Copy(h, f); err != nil {
743                 return nil, err
744         }
745         return h.Sum(nil), nil
746 }
747
748 func doLint(cs []*analysis.Analyzer, paths []string, opt *options) ([]problem, []string, error) {
749         salt, err := computeSalt()
750         if err != nil {
751                 return nil, nil, fmt.Errorf("could not compute salt for cache: %s", err)
752         }
753         cache.SetSalt(salt)
754
755         if opt == nil {
756                 opt = &options{}
757         }
758
759         l, err := newLinter(opt.Config)
760         if err != nil {
761                 return nil, nil, err
762         }
763         l.Checkers = cs
764         l.SetGoVersion(opt.GoVersion)
765         l.Runner.Stats.PrintAnalyzerMeasurement = opt.PrintAnalyzerMeasurement
766
767         cfg := &packages.Config{}
768         if opt.LintTests {
769                 cfg.Tests = true
770         }
771         if opt.Tags != "" {
772                 cfg.BuildFlags = append(cfg.BuildFlags, "-tags", opt.Tags)
773         }
774
775         printStats := func() {
776                 // Individual stats are read atomically, but overall there
777                 // is no synchronisation. For printing rough progress
778                 // information, this doesn't matter.
779                 switch l.Runner.Stats.State() {
780                 case runner.StateInitializing:
781                         fmt.Fprintln(os.Stderr, "Status: initializing")
782                 case runner.StateLoadPackageGraph:
783                         fmt.Fprintln(os.Stderr, "Status: loading package graph")
784                 case runner.StateBuildActionGraph:
785                         fmt.Fprintln(os.Stderr, "Status: building action graph")
786                 case runner.StateProcessing:
787                         fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d\n",
788                                 l.Runner.Stats.ProcessedInitialPackages(),
789                                 l.Runner.Stats.InitialPackages(),
790                                 l.Runner.Stats.ProcessedPackages(),
791                                 l.Runner.Stats.TotalPackages(),
792                                 l.Runner.ActiveWorkers(),
793                                 l.Runner.TotalWorkers(),
794                         )
795                 case runner.StateFinalizing:
796                         fmt.Fprintln(os.Stderr, "Status: finalizing")
797                 }
798         }
799         if len(infoSignals) > 0 {
800                 ch := make(chan os.Signal, 1)
801                 signal.Notify(ch, infoSignals...)
802                 defer signal.Stop(ch)
803                 go func() {
804                         for range ch {
805                                 printStats()
806                         }
807                 }()
808         }
809         return l.Lint(cfg, paths)
810 }