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