forked from reviewdog/reviewdog
/
reviewdog.go
181 lines (159 loc) · 4.14 KB
/
reviewdog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package reviewdog
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/haya14busa/reviewdog/diff"
)
// Reviewdog represents review dog application which parses result of compiler
// or linter, get diff and filter the results by diff, and report filterd
// results.
type Reviewdog struct {
toolname string
p Parser
c CommentService
d DiffService
}
// NewReviewdog returns a new Reviewdog.
func NewReviewdog(toolname string, p Parser, c CommentService, d DiffService) *Reviewdog {
return &Reviewdog{p: p, c: c, d: d, toolname: toolname}
}
// CheckResult represents a checked result of static analysis tools.
// :h error-file-format
type CheckResult struct {
Path string // relative file path
Lnum int // line number
Col int // column number (1 <tab> == 1 character column)
Message string // error message
Lines []string // Original error lines (often one line)
}
// Parser is an interface which parses compilers, linters, or any tools
// results.
type Parser interface {
Parse(r io.Reader) ([]*CheckResult, error)
}
// Comment represents a reported result as a comment.
type Comment struct {
*CheckResult
Body string
LnumDiff int
ToolName string
}
// CommentService is an interface which posts Comment.
type CommentService interface {
Post(*Comment) error
}
// DiffService is an interface which get diff.
type DiffService interface {
Diff() ([]byte, error)
Strip() int
}
// Run runs Reviewdog application.
func (w *Reviewdog) Run(r io.Reader) error {
results, err := w.p.Parse(r)
if err != nil {
return fmt.Errorf("parse error: %v", err)
}
d, err := w.d.Diff()
if err != nil {
return fmt.Errorf("fail to get diff: %v", err)
}
filediffs, err := diff.ParseMultiFile(bytes.NewReader(d))
if err != nil {
return fmt.Errorf("fail to parse diff: %v", err)
}
addedlines := addedDiffLines(filediffs, w.d.Strip())
wd, err := os.Getwd()
if err != nil {
return err
}
for _, result := range results {
addedline := addedlines.Get(result.Path, result.Lnum)
if filepath.IsAbs(result.Path) {
relpath, err := filepath.Rel(wd, result.Path)
if err != nil {
return err
}
result.Path = relpath
}
result.Path = filepath.Clean(result.Path)
if addedline != nil {
comment := &Comment{
CheckResult: result,
Body: result.Message, // TODO: format message
LnumDiff: addedline.LnumDiff,
ToolName: w.toolname,
}
if err := w.c.Post(comment); err != nil {
return err
}
}
}
return nil
}
// AddedLine represents added line in diff.
type AddedLine struct {
Path string // path to new file
Lnum int // the line number in the new file
LnumDiff int // the line number of the diff (Same as Lnumdiff of diff.Line)
Content string // line content
}
// posToAddedLine is a hash table of normalized path to line number to AddedLine.
type posToAddedLine map[string]map[int]*AddedLine
func (p posToAddedLine) Get(path string, lnum int) *AddedLine {
npath, err := normalizePath(path)
if err != nil {
return nil
}
ltodiff, ok := p[npath]
if !ok {
return nil
}
diffline, ok := ltodiff[lnum]
if !ok {
return nil
}
return diffline
}
// addedDiffLines traverse []*diff.FileDiff and returns posToAddedLine.
func addedDiffLines(filediffs []*diff.FileDiff, strip int) posToAddedLine {
r := make(posToAddedLine)
for _, filediff := range filediffs {
path := filediff.PathNew
ltodiff := make(map[int]*AddedLine)
ps := strings.Split(filepath.ToSlash(filediff.PathNew), "/")
if len(ps) > strip {
path = filepath.Join(ps[strip:]...)
}
np, err := normalizePath(path)
if err != nil {
// FIXME(haya14busa): log or return error?
continue
}
path = np
for _, hunk := range filediff.Hunks {
for _, line := range hunk.Lines {
if line.Type == diff.LineAdded {
ltodiff[line.LnumNew] = &AddedLine{
Path: path,
Lnum: line.LnumNew,
LnumDiff: line.LnumDiff,
Content: line.Content,
}
}
}
}
r[path] = ltodiff
}
return r
}
func normalizePath(p string) (string, error) {
path, err := filepath.Abs(p)
if err != nil {
return "", err
}
return filepath.ToSlash(path), nil
}