format.go raw

   1  package lintcmd
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"go/token"
   7  	"io"
   8  	"os"
   9  	"path/filepath"
  10  	"text/tabwriter"
  11  
  12  	"honnef.co/go/tools/analysis/lint"
  13  )
  14  
  15  func shortPath(path string) string {
  16  	cwd, err := os.Getwd()
  17  	if err != nil {
  18  		return path
  19  	}
  20  	if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
  21  		return rel
  22  	}
  23  	return path
  24  }
  25  
  26  func relativePositionString(pos token.Position) string {
  27  	s := shortPath(pos.Filename)
  28  	if pos.IsValid() {
  29  		if s != "" {
  30  			s += ":"
  31  		}
  32  		s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
  33  	}
  34  	if s == "" {
  35  		s = "-"
  36  	}
  37  	return s
  38  }
  39  
  40  type statter interface {
  41  	Stats(total, errors, warnings, ignored int)
  42  }
  43  
  44  type formatter interface {
  45  	Format(checks []*lint.Analyzer, diagnostics []diagnostic)
  46  }
  47  
  48  type textFormatter struct {
  49  	W io.Writer
  50  }
  51  
  52  func (o textFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
  53  	for _, p := range ps {
  54  		fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Position), p.String())
  55  		for _, r := range p.Related {
  56  			fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Position), r.Message)
  57  		}
  58  	}
  59  }
  60  
  61  type nullFormatter struct{}
  62  
  63  func (nullFormatter) Format([]*lint.Analyzer, []diagnostic) {}
  64  
  65  type jsonFormatter struct {
  66  	W io.Writer
  67  }
  68  
  69  func (o jsonFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
  70  	type location struct {
  71  		File   string `json:"file"`
  72  		Line   int    `json:"line"`
  73  		Column int    `json:"column"`
  74  	}
  75  	type related struct {
  76  		Location location `json:"location"`
  77  		End      location `json:"end"`
  78  		Message  string   `json:"message"`
  79  	}
  80  
  81  	enc := json.NewEncoder(o.W)
  82  	for _, p := range ps {
  83  		jp := struct {
  84  			Code     string    `json:"code"`
  85  			Severity string    `json:"severity,omitempty"`
  86  			Location location  `json:"location"`
  87  			End      location  `json:"end"`
  88  			Message  string    `json:"message"`
  89  			Related  []related `json:"related,omitempty"`
  90  		}{
  91  			Code:     p.Category,
  92  			Severity: p.Severity.String(),
  93  			Location: location{
  94  				File:   p.Position.Filename,
  95  				Line:   p.Position.Line,
  96  				Column: p.Position.Column,
  97  			},
  98  			End: location{
  99  				File:   p.End.Filename,
 100  				Line:   p.End.Line,
 101  				Column: p.End.Column,
 102  			},
 103  			Message: p.Message,
 104  		}
 105  		for _, r := range p.Related {
 106  			jp.Related = append(jp.Related, related{
 107  				Location: location{
 108  					File:   r.Position.Filename,
 109  					Line:   r.Position.Line,
 110  					Column: r.Position.Column,
 111  				},
 112  				End: location{
 113  					File:   r.End.Filename,
 114  					Line:   r.End.Line,
 115  					Column: r.End.Column,
 116  				},
 117  				Message: r.Message,
 118  			})
 119  		}
 120  		_ = enc.Encode(jp)
 121  	}
 122  }
 123  
 124  type stylishFormatter struct {
 125  	W io.Writer
 126  
 127  	prevFile string
 128  	tw       *tabwriter.Writer
 129  }
 130  
 131  func (o *stylishFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
 132  	for _, p := range ps {
 133  		pos := p.Position
 134  		if pos.Filename == "" {
 135  			pos.Filename = "-"
 136  		}
 137  
 138  		if pos.Filename != o.prevFile {
 139  			if o.prevFile != "" {
 140  				o.tw.Flush()
 141  				fmt.Fprintln(o.W)
 142  			}
 143  			fmt.Fprintln(o.W, pos.Filename)
 144  			o.prevFile = pos.Filename
 145  			o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
 146  		}
 147  		fmt.Fprintf(o.tw, "  (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Category, p.Message)
 148  		for _, r := range p.Related {
 149  			fmt.Fprintf(o.tw, "    (%d, %d)\t\t  %s\n", r.Position.Line, r.Position.Column, r.Message)
 150  		}
 151  	}
 152  }
 153  
 154  func (o *stylishFormatter) Stats(total, errors, warnings, ignored int) {
 155  	if o.tw != nil {
 156  		o.tw.Flush()
 157  		fmt.Fprintln(o.W)
 158  	}
 159  	fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n",
 160  		total, errors, warnings, ignored)
 161  }
 162