Skip to content

Commit 2e9d8f4

Browse files
committed
Support SuggestedFixes
Conver the SuggesterFixes to Replacement by: 1. applying the fixes to a file in memory 2. creating a diff by comparing with original file 3. using `ExtractIssuesFromPatch` function to create the issues
1 parent 2f68995 commit 2e9d8f4

File tree

9 files changed

+190
-44
lines changed

9 files changed

+190
-44
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ require (
6464
github.com/nishanths/exhaustive v0.7.11
6565
github.com/nishanths/predeclared v0.2.1
6666
github.com/pkg/errors v0.9.1
67+
github.com/pmezard/go-difflib v1.0.0
6768
github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b
6869
github.com/prometheus/procfs v0.6.0 // indirect
6970
github.com/quasilyte/go-ruleguard/dsl v0.3.17

pkg/golinters/gofmt_common.go renamed to pkg/golinters/goanalysis/extract_issues.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
package golinters
1+
package goanalysis
22

33
import (
44
"bytes"
55
"fmt"
66
"go/token"
77
"strings"
8+
"sync"
89

910
"github.com/pkg/errors"
1011
diffpkg "github.com/sourcegraph/go-diff/diff"
@@ -207,29 +208,30 @@ func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
207208
return p.ret
208209
}
209210

211+
type TextModifier = func(lintCtx *linter.Context, text string) string
212+
213+
var (
214+
textModifier = make(map[string]TextModifier)
215+
textModifierLock = sync.RWMutex{}
216+
)
217+
218+
func SetTextModifier(linterName string, modifier TextModifier) {
219+
textModifierLock.Lock()
220+
defer textModifierLock.Unlock()
221+
textModifier[linterName] = modifier
222+
}
223+
210224
func getErrorTextForLinter(lintCtx *linter.Context, linterName string) string {
211225
text := "File is not formatted"
212-
switch linterName {
213-
case gofumptName:
214-
text = "File is not `gofumpt`-ed"
215-
if lintCtx.Settings().Gofumpt.ExtraRules {
216-
text += " with `-extra`"
217-
}
218-
case gofmtName:
219-
text = "File is not `gofmt`-ed"
220-
if lintCtx.Settings().Gofmt.Simplify {
221-
text += " with `-s`"
222-
}
223-
case goimportsName:
224-
text = "File is not `goimports`-ed"
225-
if lintCtx.Settings().Goimports.LocalPrefixes != "" {
226-
text += " with -local " + lintCtx.Settings().Goimports.LocalPrefixes
227-
}
226+
textModifierLock.RLock()
227+
defer textModifierLock.RUnlock()
228+
if f, ok := textModifier[linterName]; ok {
229+
return f(lintCtx, text)
228230
}
229231
return text
230232
}
231233

232-
func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, linterName string) ([]result.Issue, error) {
234+
func ExtractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, linterName string) ([]result.Issue, error) {
233235
diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
234236
if err != nil {
235237
return nil, errors.Wrap(err, "can't parse patch")

pkg/golinters/gofmt_test.go renamed to pkg/golinters/goanalysis/extract_issues_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package golinters
1+
package goanalysis
22

33
import (
44
"testing"

pkg/golinters/goanalysis/runners.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss
5959
}
6060
}()
6161

62-
buildAllIssues := func() []result.Issue {
62+
buildAllIssues := func() ([]result.Issue, error) {
6363
var retIssues []result.Issue
6464
reportedIssues := cfg.reportIssues(lintCtx)
6565
for i := range reportedIssues {
@@ -69,8 +69,13 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss
6969
}
7070
retIssues = append(retIssues, *issue)
7171
}
72-
retIssues = append(retIssues, buildIssues(diags, cfg.getLinterNameForDiagnostic)...)
73-
return retIssues
72+
newIssues, err := buildIssues(lintCtx, diags, cfg.getLinterNameForDiagnostic)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
retIssues = append(retIssues, newIssues...)
78+
return retIssues, nil
7479
}
7580

7681
errIssues, err := buildIssuesFromIllTypedError(errs, lintCtx)
@@ -79,12 +84,16 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss
7984
}
8085

8186
issues = append(issues, errIssues...)
82-
issues = append(issues, buildAllIssues()...)
87+
newIssues, err := buildAllIssues()
88+
if err != nil {
89+
return nil, err
90+
}
91+
issues = append(issues, newIssues...)
8392

8493
return issues, nil
8594
}
8695

87-
func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) string) []result.Issue {
96+
func buildIssues(lintCtx *linter.Context, diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) string) ([]result.Issue, error) {
8897
var issues []result.Issue
8998
for i := range diags {
9099
diag := &diags[i]
@@ -97,25 +106,31 @@ func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) st
97106
text = fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message)
98107
}
99108

100-
issues = append(issues, result.Issue{
101-
FromLinter: linterName,
102-
Text: text,
103-
Pos: diag.Position,
104-
Pkg: diag.Pkg,
105-
})
106-
107-
if len(diag.Related) > 0 {
108-
for _, info := range diag.Related {
109-
issues = append(issues, result.Issue{
110-
FromLinter: linterName,
111-
Text: fmt.Sprintf("%s(related information): %s", diag.Analyzer.Name, info.Message),
112-
Pos: diag.Pkg.Fset.Position(info.Pos),
113-
Pkg: diag.Pkg,
114-
})
109+
if lintCtx.Cfg.Issues.NeedFix && len(diag.SuggestedFixes) > 0 {
110+
newIssues, err := convertSuggestedFixes(lintCtx, linterName, diag)
111+
if err != nil {
112+
return nil, err
115113
}
114+
issues = append(issues, newIssues...)
115+
} else {
116+
issues = append(issues, result.Issue{
117+
FromLinter: linterName,
118+
Text: text,
119+
Pos: diag.Position,
120+
Pkg: diag.Pkg,
121+
})
122+
}
123+
124+
for _, info := range diag.Related {
125+
issues = append(issues, result.Issue{
126+
FromLinter: linterName,
127+
Text: fmt.Sprintf("%s(related information): %s", diag.Analyzer.Name, info.Message),
128+
Pos: diag.Pkg.Fset.Position(info.Pos),
129+
Pkg: diag.Pkg,
130+
})
116131
}
117132
}
118-
return issues
133+
return issues, nil
119134
}
120135

121136
func getIssuesCacheKey(analyzers []*analysis.Analyzer) string {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package goanalysis
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"go/token"
7+
"io"
8+
"os"
9+
10+
"github.com/pkg/errors"
11+
"github.com/pmezard/go-difflib/difflib"
12+
13+
"github.com/golangci/golangci-lint/pkg/lint/linter"
14+
"github.com/golangci/golangci-lint/pkg/result"
15+
)
16+
17+
func convertSuggestedFixes(lintCtx *linter.Context, linterName string, diag *Diagnostic) ([]result.Issue, error) {
18+
var issues []result.Issue
19+
20+
for _, fix := range diag.SuggestedFixes {
21+
origContent := make(map[string][]byte)
22+
readers := make(map[string]io.ReadSeeker)
23+
bufs := make(map[string]*bytes.Buffer)
24+
25+
for _, edit := range fix.TextEdits {
26+
if edit.End == token.NoPos {
27+
edit.End = edit.Pos
28+
}
29+
start, end := diag.Pkg.Fset.Position(edit.Pos), diag.Pkg.Fset.Position(edit.End)
30+
orig, ok := readers[start.Filename]
31+
if !ok {
32+
data, err := os.ReadFile(start.Filename)
33+
if err != nil {
34+
return nil, errors.Wrapf(err, "can't read file: %s", start.Filename)
35+
}
36+
origContent[start.Filename] = data
37+
orig = bytes.NewReader(data)
38+
readers[start.Filename] = orig
39+
bufs[start.Filename] = &bytes.Buffer{}
40+
}
41+
42+
buf := bufs[start.Filename]
43+
cur, err := orig.Seek(0, io.SeekCurrent)
44+
if err != nil {
45+
return nil, errors.Wrapf(err, "can't get current position of reader of file %q", start.Filename)
46+
}
47+
if l := start.Offset - int(cur); l > 0 {
48+
b := make([]byte, l)
49+
if _, err := orig.Read(b); err != nil {
50+
return nil, errors.Wrapf(err, "can't read form stored reader of file %q", start.Filename)
51+
}
52+
buf.Write(b)
53+
}
54+
buf.Write(edit.NewText)
55+
if _, err := orig.Seek(int64(end.Offset), io.SeekStart); err != nil {
56+
return nil, errors.Wrapf(err, "can't change position of reader of file %q", start.Filename)
57+
}
58+
}
59+
for filename, f := range readers {
60+
data, err := io.ReadAll(f)
61+
if err != nil {
62+
return nil, err
63+
}
64+
bufs[filename].Write(data)
65+
}
66+
67+
for filename, data := range origContent {
68+
newIssues, err := createPatchAndExtractIssues(lintCtx, linterName, filename, data, bufs[filename].Bytes())
69+
if err != nil {
70+
return nil, err
71+
}
72+
for i := range newIssues {
73+
newIssues[i].Text = fmt.Sprintf("%s: %s", diag.Message, fix.Message)
74+
}
75+
issues = append(issues, newIssues...)
76+
}
77+
}
78+
79+
return issues, nil
80+
}
81+
82+
func createPatchAndExtractIssues(lintCtx *linter.Context, linterName, filename string, src, dst []byte) ([]result.Issue, error) {
83+
out := bytes.Buffer{}
84+
if _, err := out.WriteString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", filename)); err != nil {
85+
return nil, errors.Wrap(err, "can't write diff header")
86+
}
87+
88+
d := difflib.UnifiedDiff{
89+
A: difflib.SplitLines(string(src)),
90+
B: difflib.SplitLines(string(dst)),
91+
Context: 3,
92+
}
93+
94+
if err := difflib.WriteUnifiedDiff(&out, d); err != nil {
95+
return nil, errors.Wrap(err, "can't create diff")
96+
}
97+
98+
newIssues, err := ExtractIssuesFromPatch(out.String(), lintCtx.Log, lintCtx, linterName)
99+
if err != nil {
100+
return nil, errors.Wrap(err, "can't extract issues from diff")
101+
}
102+
103+
return newIssues, nil
104+
}

pkg/golinters/gofmt.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import (
1414
const gofmtName = "gofmt"
1515

1616
func NewGofmt() *goanalysis.Linter {
17+
goanalysis.SetTextModifier(gofmtName, func(lintCtx *linter.Context, _ string) string {
18+
text := "File is not `gofmt`-ed"
19+
if lintCtx.Settings().Gofmt.Simplify {
20+
text += " with `-s`"
21+
}
22+
return text
23+
})
24+
1725
var mu sync.Mutex
1826
var resIssues []goanalysis.Issue
1927

@@ -46,7 +54,7 @@ func NewGofmt() *goanalysis.Linter {
4654
continue
4755
}
4856

49-
is, err := extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, gofmtName)
57+
is, err := goanalysis.ExtractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, gofmtName)
5058
if err != nil {
5159
return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff))
5260
}

pkg/golinters/gofumpt.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ import (
1919
const gofumptName = "gofumpt"
2020

2121
func NewGofumpt() *goanalysis.Linter {
22+
goanalysis.SetTextModifier(gofumptName, func(lintCtx *linter.Context, _ string) string {
23+
text := "File is not `gofumpt`-ed"
24+
if lintCtx.Settings().Gofumpt.ExtraRules {
25+
text += " with `-extra`"
26+
}
27+
return text
28+
})
29+
2230
var mu sync.Mutex
2331
var resIssues []goanalysis.Issue
2432
differ := difflib.New()
@@ -73,7 +81,7 @@ func NewGofumpt() *goanalysis.Linter {
7381
}
7482

7583
diff := out.String()
76-
is, err := extractIssuesFromPatch(diff, lintCtx.Log, lintCtx, gofumptName)
84+
is, err := goanalysis.ExtractIssuesFromPatch(diff, lintCtx.Log, lintCtx, gofumptName)
7785
if err != nil {
7886
return nil, errors.Wrapf(err, "can't extract issues from gofumpt diff output %q", diff)
7987
}

pkg/golinters/goimports.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import (
1515
const goimportsName = "goimports"
1616

1717
func NewGoimports() *goanalysis.Linter {
18+
goanalysis.SetTextModifier(goimportsName, func(lintCtx *linter.Context, _ string) string {
19+
text := "File is not `goimports`-ed"
20+
if lintCtx.Settings().Goimports.LocalPrefixes != "" {
21+
text += " with -local " + lintCtx.Settings().Goimports.LocalPrefixes
22+
}
23+
return text
24+
})
25+
1826
var mu sync.Mutex
1927
var resIssues []goanalysis.Issue
2028

@@ -47,7 +55,7 @@ func NewGoimports() *goanalysis.Linter {
4755
continue
4856
}
4957

50-
is, err := extractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, goimportsName)
58+
is, err := goanalysis.ExtractIssuesFromPatch(string(diff), lintCtx.Log, lintCtx, goimportsName)
5159
if err != nil {
5260
return nil, errors.Wrapf(err, "can't extract issues from gofmt diff output %q", string(diff))
5361
}

pkg/result/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Range struct {
1414

1515
type Replacement struct {
1616
NeedOnlyDelete bool // need to delete all lines of the issue without replacement with new lines
17-
NewLines []string // if NeedDelete is false it's the replacement lines
17+
NewLines []string // if NeedOnlyDelete is false it's the replacement lines
1818
Inline *InlineFix
1919
}
2020

0 commit comments

Comments
 (0)