Skip to content

Commit 19a66e6

Browse files
committed
chore: rewrite diff hunkChangesParser
1 parent 0acc998 commit 19a66e6

12 files changed

+388
-431
lines changed

pkg/golinters/internal/diff.go

Lines changed: 127 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@ package internal
33
import (
44
"bytes"
55
"fmt"
6+
"go/ast"
67
"go/token"
8+
"slices"
79
"strings"
810

911
diffpkg "github.com/sourcegraph/go-diff/diff"
12+
"golang.org/x/tools/go/analysis"
1013

1114
"github.com/golangci/golangci-lint/pkg/config"
15+
"github.com/golangci/golangci-lint/pkg/goanalysis"
1216
"github.com/golangci/golangci-lint/pkg/lint/linter"
1317
"github.com/golangci/golangci-lint/pkg/logutils"
14-
"github.com/golangci/golangci-lint/pkg/result"
1518
)
1619

1720
type Change struct {
18-
LineRange result.Range
19-
Replacement result.Replacement
21+
From, To int
22+
NewLines []string
2023
}
2124

2225
type diffLineType string
@@ -44,58 +47,48 @@ type hunkChangesParser struct {
4447

4548
log logutils.Log
4649

47-
lines []diffLine
48-
49-
ret []Change
50+
changes []Change
5051
}
5152

52-
func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
53-
lines := bytes.Split(h.Body, []byte{'\n'})
54-
currentOriginalLineNumber := int(h.OrigStartLine)
55-
var ret []diffLine
53+
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
54+
lines := parseDiffLines(h)
5655

57-
for i, line := range lines {
58-
dl := diffLine{
59-
originalNumber: currentOriginalLineNumber,
56+
for i := 0; i < len(lines); {
57+
line := lines[i]
58+
59+
if line.typ == diffLineOriginal {
60+
p.handleOriginalLine(lines, line, &i)
61+
continue
6062
}
6163

62-
lineStr := string(line)
64+
var deletedLines []diffLine
65+
for ; i < len(lines) && lines[i].typ == diffLineDeleted; i++ {
66+
deletedLines = append(deletedLines, lines[i])
67+
}
6368

64-
if strings.HasPrefix(lineStr, "-") {
65-
dl.typ = diffLineDeleted
66-
dl.data = strings.TrimPrefix(lineStr, "-")
67-
currentOriginalLineNumber++
68-
} else if strings.HasPrefix(lineStr, "+") {
69-
dl.typ = diffLineAdded
70-
dl.data = strings.TrimPrefix(lineStr, "+")
71-
} else {
72-
if i == len(lines)-1 && lineStr == "" {
73-
// handle last \n: don't add an empty original line
74-
break
75-
}
69+
var addedLines []string
70+
for ; i < len(lines) && lines[i].typ == diffLineAdded; i++ {
71+
addedLines = append(addedLines, lines[i].data)
72+
}
7673

77-
dl.typ = diffLineOriginal
78-
dl.data = strings.TrimPrefix(lineStr, " ")
79-
currentOriginalLineNumber++
74+
if len(deletedLines) != 0 {
75+
p.handleDeletedLines(deletedLines, addedLines)
76+
continue
8077
}
8178

82-
ret = append(ret, dl)
79+
// no deletions, only additions
80+
p.handleAddedOnlyLines(addedLines)
8381
}
8482

85-
// if > 0, then the original file had a 'No newline at end of file' mark
86-
if h.OrigNoNewlineAt > 0 {
87-
dl := diffLine{
88-
originalNumber: currentOriginalLineNumber + 1,
89-
typ: diffLineAdded,
90-
data: "",
91-
}
92-
ret = append(ret, dl)
83+
if len(p.replacementLinesToPrepend) != 0 {
84+
p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", lines)
85+
return nil
9386
}
9487

95-
p.lines = ret
88+
return p.changes
9689
}
9790

98-
func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
91+
func (p *hunkChangesParser) handleOriginalLine(lines []diffLine, line diffLine, i *int) {
9992
if len(p.replacementLinesToPrepend) == 0 {
10093
p.lastOriginalLine = &line
10194
*i++
@@ -109,51 +102,39 @@ func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
109102

110103
*i++
111104
var followingAddedLines []string
112-
for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
113-
followingAddedLines = append(followingAddedLines, p.lines[*i].data)
105+
for ; *i < len(lines) && lines[*i].typ == diffLineAdded; *i++ {
106+
followingAddedLines = append(followingAddedLines, lines[*i].data)
107+
}
108+
109+
change := Change{
110+
From: line.originalNumber,
111+
To: line.originalNumber,
112+
NewLines: slices.Concat(p.replacementLinesToPrepend, []string{line.data}, followingAddedLines),
114113
}
114+
p.changes = append(p.changes, change)
115115

116-
p.ret = append(p.ret, Change{
117-
LineRange: result.Range{
118-
From: line.originalNumber,
119-
To: line.originalNumber,
120-
},
121-
Replacement: result.Replacement{
122-
NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
123-
},
124-
})
125116
p.replacementLinesToPrepend = nil
126117
p.lastOriginalLine = &line
127118
}
128119

129120
func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
130121
change := Change{
131-
LineRange: result.Range{
132-
From: deletedLines[0].originalNumber,
133-
To: deletedLines[len(deletedLines)-1].originalNumber,
134-
},
122+
From: deletedLines[0].originalNumber,
123+
To: deletedLines[len(deletedLines)-1].originalNumber,
135124
}
136125

137-
if len(addedLines) != 0 {
138-
change.Replacement.NewLines = append([]string{}, p.replacementLinesToPrepend...)
139-
change.Replacement.NewLines = append(change.Replacement.NewLines, addedLines...)
140-
if len(p.replacementLinesToPrepend) != 0 {
141-
p.replacementLinesToPrepend = nil
142-
}
143-
144-
p.ret = append(p.ret, change)
145-
return
146-
}
126+
switch {
127+
case len(addedLines) != 0:
128+
change.NewLines = slices.Concat(p.replacementLinesToPrepend, addedLines)
129+
p.replacementLinesToPrepend = nil
147130

148-
// delete-only change with possible prepending
149-
if len(p.replacementLinesToPrepend) != 0 {
150-
change.Replacement.NewLines = p.replacementLinesToPrepend
131+
case len(p.replacementLinesToPrepend) != 0:
132+
// delete-only change with possible prepending
133+
change.NewLines = slices.Clone(p.replacementLinesToPrepend)
151134
p.replacementLinesToPrepend = nil
152-
} else {
153-
change.Replacement.NeedOnlyDelete = true
154135
}
155136

156-
p.ret = append(p.ret, change)
137+
p.changes = append(p.changes, change)
157138
}
158139

159140
func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
@@ -166,70 +147,88 @@ func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
166147
// 2. ...
167148

168149
p.replacementLinesToPrepend = addedLines
150+
169151
return
170152
}
171153

172154
// add-only change merged into the last original line with possible prepending
173-
p.ret = append(p.ret, Change{
174-
LineRange: result.Range{
175-
From: p.lastOriginalLine.originalNumber,
176-
To: p.lastOriginalLine.originalNumber,
177-
},
178-
Replacement: result.Replacement{
179-
NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
180-
},
181-
})
155+
change := Change{
156+
From: p.lastOriginalLine.originalNumber,
157+
To: p.lastOriginalLine.originalNumber,
158+
NewLines: slices.Concat(p.replacementLinesToPrepend, []string{p.lastOriginalLine.data}, addedLines),
159+
}
160+
161+
p.changes = append(p.changes, change)
162+
182163
p.replacementLinesToPrepend = nil
183164
}
184165

185-
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
186-
p.parseDiffLines(h)
166+
func parseDiffLines(h *diffpkg.Hunk) []diffLine {
167+
lines := bytes.Split(h.Body, []byte{'\n'})
187168

188-
for i := 0; i < len(p.lines); {
189-
line := p.lines[i]
190-
if line.typ == diffLineOriginal {
191-
p.handleOriginalLine(line, &i)
192-
continue
193-
}
169+
currentOriginalLineNumber := int(h.OrigStartLine)
194170

195-
var deletedLines []diffLine
196-
for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
197-
deletedLines = append(deletedLines, p.lines[i])
171+
var diffLines []diffLine
172+
173+
for i, line := range lines {
174+
dl := diffLine{
175+
originalNumber: currentOriginalLineNumber,
198176
}
199177

200-
var addedLines []string
201-
for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
202-
addedLines = append(addedLines, p.lines[i].data)
178+
if i == len(lines)-1 && len(line) == 0 {
179+
// handle last \n: don't add an empty original line
180+
break
203181
}
204182

205-
if len(deletedLines) != 0 {
206-
p.handleDeletedLines(deletedLines, addedLines)
207-
continue
183+
lineStr := string(line)
184+
185+
switch {
186+
case strings.HasPrefix(lineStr, "-"):
187+
dl.typ = diffLineDeleted
188+
dl.data = strings.TrimPrefix(lineStr, "-")
189+
currentOriginalLineNumber++
190+
191+
case strings.HasPrefix(lineStr, "+"):
192+
dl.typ = diffLineAdded
193+
dl.data = strings.TrimPrefix(lineStr, "+")
194+
195+
default:
196+
dl.typ = diffLineOriginal
197+
dl.data = strings.TrimPrefix(lineStr, " ")
198+
currentOriginalLineNumber++
208199
}
209200

210-
// no deletions, only additions
211-
p.handleAddedOnlyLines(addedLines)
201+
diffLines = append(diffLines, dl)
212202
}
213203

214-
if len(p.replacementLinesToPrepend) != 0 {
215-
p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
216-
return nil
204+
// if > 0, then the original file had a 'No newline at end of file' mark
205+
if h.OrigNoNewlineAt > 0 {
206+
dl := diffLine{
207+
originalNumber: currentOriginalLineNumber + 1,
208+
typ: diffLineAdded,
209+
data: "",
210+
}
211+
diffLines = append(diffLines, dl)
217212
}
218213

219-
return p.ret
214+
return diffLines
220215
}
221216

222-
func ExtractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName string, formatter fmtTextFormatter) ([]result.Issue, error) {
217+
func ExtractDiagnosticFromPatch(pass *analysis.Pass, file *ast.File, patch string,
218+
lintCtx *linter.Context, formatter fmtTextFormatter) error {
223219
diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
224220
if err != nil {
225-
return nil, fmt.Errorf("can't parse patch: %w", err)
221+
return fmt.Errorf("can't parse patch: %w", err)
226222
}
227223

228224
if len(diffs) == 0 {
229-
return nil, fmt.Errorf("got no diffs from patch parser: %v", patch)
225+
return fmt.Errorf("got no diffs from patch parser: %v", patch)
230226
}
231227

232-
var issues []result.Issue
228+
ft := pass.Fset.File(file.Pos())
229+
230+
adjLine := pass.Fset.PositionFor(file.Pos(), false).Line - pass.Fset.PositionFor(file.Pos(), true).Line
231+
233232
for _, d := range diffs {
234233
if len(d.Hunks) == 0 {
235234
lintCtx.Log.Warnf("Got no hunks in diff %+v", d)
@@ -242,23 +241,29 @@ func ExtractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName st
242241
changes := p.parse(hunk)
243242

244243
for _, change := range changes {
245-
i := result.Issue{
246-
FromLinter: linterName,
247-
Pos: token.Position{
248-
Filename: d.NewName,
249-
Line: change.LineRange.From,
250-
},
251-
Text: formatter(lintCtx.Settings()),
252-
Replacement: &change.Replacement,
253-
}
254-
if change.LineRange.From != change.LineRange.To {
255-
i.LineRange = &change.LineRange
256-
}
257-
258-
issues = append(issues, i)
244+
pass.Report(toDiagnostic(ft, change, formatter(lintCtx.Settings()), adjLine))
259245
}
260246
}
261247
}
262248

263-
return issues, nil
249+
return nil
250+
}
251+
252+
func toDiagnostic(ft *token.File, change Change, message string, adjLine int) analysis.Diagnostic {
253+
start := ft.LineStart(change.From + adjLine)
254+
255+
end := goanalysis.EndOfLinePos(ft, change.To+adjLine)
256+
257+
return analysis.Diagnostic{
258+
Pos: start,
259+
End: end,
260+
Message: message, // TODO(ldez) change message formatter to have a better message.
261+
SuggestedFixes: []analysis.SuggestedFix{{
262+
TextEdits: []analysis.TextEdit{{
263+
Pos: start,
264+
End: end,
265+
NewText: []byte(strings.Join(change.NewLines, "\n")),
266+
}},
267+
}},
268+
}
264269
}

0 commit comments

Comments
 (0)