github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/lintcmd/format.go (about) 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 "github.com/amarpal/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 }