// Package format provides formatters for linter problems. package format import ( "encoding/json" "fmt" "go/token" "io" "os" "path/filepath" "text/tabwriter" "honnef.co/go/tools/lint" ) func shortPath(path string) string { cwd, err := os.Getwd() if err != nil { return path } if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) { return rel } return path } func relativePositionString(pos token.Position) string { s := shortPath(pos.Filename) if pos.IsValid() { if s != "" { s += ":" } s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) } if s == "" { s = "-" } return s } type Statter interface { Stats(total, errors, warnings, ignored int) } type Formatter interface { Format(p lint.Problem) } type Text struct { W io.Writer } func (o Text) Format(p lint.Problem) { fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Pos), p.String()) for _, r := range p.Related { fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Pos), r.Message) } } type JSON struct { W io.Writer } func severity(s lint.Severity) string { switch s { case lint.Error: return "error" case lint.Warning: return "warning" case lint.Ignored: return "ignored" } return "" } func (o JSON) Format(p lint.Problem) { type location struct { File string `json:"file"` Line int `json:"line"` Column int `json:"column"` } type related struct { Location location `json:"location"` End location `json:"end"` Message string `json:"message"` } jp := struct { Code string `json:"code"` Severity string `json:"severity,omitempty"` Location location `json:"location"` End location `json:"end"` Message string `json:"message"` Related []related `json:"related,omitempty"` }{ Code: p.Check, Severity: severity(p.Severity), Location: location{ File: p.Pos.Filename, Line: p.Pos.Line, Column: p.Pos.Column, }, End: location{ File: p.End.Filename, Line: p.End.Line, Column: p.End.Column, }, Message: p.Message, } for _, r := range p.Related { jp.Related = append(jp.Related, related{ Location: location{ File: r.Pos.Filename, Line: r.Pos.Line, Column: r.Pos.Column, }, End: location{ File: r.End.Filename, Line: r.End.Line, Column: r.End.Column, }, Message: r.Message, }) } _ = json.NewEncoder(o.W).Encode(jp) } type Stylish struct { W io.Writer prevFile string tw *tabwriter.Writer } func (o *Stylish) Format(p lint.Problem) { pos := p.Pos if pos.Filename == "" { pos.Filename = "-" } if pos.Filename != o.prevFile { if o.prevFile != "" { o.tw.Flush() fmt.Fprintln(o.W) } fmt.Fprintln(o.W, pos.Filename) o.prevFile = pos.Filename o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0) } fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Check, p.Message) for _, r := range p.Related { fmt.Fprintf(o.tw, " (%d, %d)\t\t %s\n", r.Pos.Line, r.Pos.Column, r.Message) } } func (o *Stylish) Stats(total, errors, warnings, ignored int) { if o.tw != nil { o.tw.Flush() fmt.Fprintln(o.W) } fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n", total, errors, warnings, ignored) }