@@ -2,6 +2,7 @@ package goanalysis
2
2
3
3
import (
4
4
"fmt"
5
+ "go/token"
5
6
"runtime"
6
7
"sort"
7
8
"strings"
@@ -34,7 +35,14 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss
34
35
const stagesToPrint = 10
35
36
defer sw .PrintTopStages (stagesToPrint )
36
37
37
- runner := newRunner (cfg .getName (), log , lintCtx .PkgCache , lintCtx .LoadGuard , cfg .getLoadMode (), sw )
38
+ runner := newRunner (
39
+ cfg .getName (),
40
+ log ,
41
+ lintCtx .PkgCache ,
42
+ lintCtx .LoadGuard ,
43
+ cfg .getLoadMode (),
44
+ sw ,
45
+ )
38
46
39
47
pkgs := lintCtx .Packages
40
48
if cfg .useOriginalPackages () {
@@ -84,38 +92,70 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss
84
92
return issues , nil
85
93
}
86
94
87
- func buildIssues (diags []Diagnostic , linterNameBuilder func (diag * Diagnostic ) string ) []result.Issue {
95
+ func buildIssues (
96
+ diags []Diagnostic ,
97
+ linterNameBuilder func (diag * Diagnostic ) string ,
98
+ ) []result.Issue {
88
99
var issues []result.Issue
89
100
for i := range diags {
90
101
diag := & diags [i ]
91
- linterName := linterNameBuilder (diag )
102
+ issues = append (issues , buildSingleIssue (diag , linterNameBuilder (diag )))
103
+ }
104
+ return issues
105
+ }
92
106
93
- var text string
94
- if diag .Analyzer .Name == linterName {
95
- text = diag .Message
96
- } else {
97
- text = fmt .Sprintf ("%s: %s" , diag .Analyzer .Name , diag .Message )
98
- }
107
+ func buildSingleIssue (diag * Diagnostic , linterName string ) result.Issue {
108
+ text := generateIssueText (diag , linterName )
109
+ issue := result.Issue {
110
+ FromLinter : linterName ,
111
+ Text : text ,
112
+ Pos : diag .Position ,
113
+ Pkg : diag .Pkg ,
114
+ }
115
+
116
+ if len (diag .SuggestedFixes ) > 0 {
117
+ // Don't really have a better way of picking a best fix right now
118
+ chosenFix := diag .SuggestedFixes [0 ]
119
+
120
+ // It could be confusing to return more than one issue per single diagnostic,
121
+ // but if we return a subset it might be a partial application of a fix. Don't
122
+ // apply a fix unless there is only one for now
123
+ if len (chosenFix .TextEdits ) == 1 {
124
+ edit := chosenFix .TextEdits [0 ]
125
+
126
+ pos := diag .Pkg .Fset .Position (edit .Pos )
127
+ end := diag .Pkg .Fset .Position (edit .End )
128
+
129
+ newLines := strings .Split (string (edit .NewText ), "\n " )
99
130
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
- })
131
+ // This only works if we're only replacing whole lines with brand-new lines
132
+ if onlyReplacesWholeLines (pos , end , newLines ) {
133
+ // both original and new content ends with newline,
134
+ // omit to avoid partial line replacement
135
+ newLines = newLines [:len (newLines )- 1 ]
136
+
137
+ issue .Replacement = & result.Replacement {NewLines : newLines }
138
+ issue .LineRange = & result.Range {From : pos .Line , To : end .Line - 1 }
139
+
140
+ return issue
115
141
}
116
142
}
117
143
}
118
- return issues
144
+
145
+ return issue
146
+ }
147
+
148
+ func onlyReplacesWholeLines (oPos , oEnd token.Position , newLines []string ) bool {
149
+ return oPos .Column == 1 && oEnd .Column == 1 &&
150
+ oPos .Line < oEnd .Line && // must be replacing at least one line
151
+ newLines [len (newLines )- 1 ] == "" // edit.NewText ended with '\n'
152
+ }
153
+
154
+ func generateIssueText (diag * Diagnostic , linterName string ) string {
155
+ if diag .Analyzer .Name == linterName {
156
+ return diag .Message
157
+ }
158
+ return fmt .Sprintf ("%s: %s" , diag .Analyzer .Name , diag .Message )
119
159
}
120
160
121
161
func getIssuesCacheKey (analyzers []* analysis.Analyzer ) string {
@@ -160,7 +200,12 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.
160
200
161
201
atomic .AddInt32 (& savedIssuesCount , int32 (len (encodedIssues )))
162
202
if err := lintCtx .PkgCache .Put (pkg , pkgcache .HashModeNeedAllDeps , lintResKey , encodedIssues ); err != nil {
163
- lintCtx .Log .Infof ("Failed to save package %s issues (%d) to cache: %s" , pkg , len (pkgIssues ), err )
203
+ lintCtx .Log .Infof (
204
+ "Failed to save package %s issues (%d) to cache: %s" ,
205
+ pkg ,
206
+ len (pkgIssues ),
207
+ err ,
208
+ )
164
209
} else {
165
210
issuesCacheDebugf ("Saved package %s issues (%d) to cache" , pkg , len (pkgIssues ))
166
211
}
@@ -178,7 +223,12 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.
178
223
close (pkgCh )
179
224
wg .Wait ()
180
225
181
- issuesCacheDebugf ("Saved %d issues from %d packages to cache in %s" , savedIssuesCount , len (allPkgs ), time .Since (startedAt ))
226
+ issuesCacheDebugf (
227
+ "Saved %d issues from %d packages to cache in %s" ,
228
+ savedIssuesCount ,
229
+ len (allPkgs ),
230
+ time .Since (startedAt ),
231
+ )
182
232
}
183
233
184
234
//nolint:gocritic
@@ -206,7 +256,12 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context,
206
256
defer wg .Done ()
207
257
for pkg := range pkgCh {
208
258
var pkgIssues []EncodingIssue
209
- err := lintCtx .PkgCache .Get (pkg , pkgcache .HashModeNeedAllDeps , lintResKey , & pkgIssues )
259
+ err := lintCtx .PkgCache .Get (
260
+ pkg ,
261
+ pkgcache .HashModeNeedAllDeps ,
262
+ lintResKey ,
263
+ & pkgIssues ,
264
+ )
210
265
cacheRes := pkgToCacheRes [pkg ]
211
266
cacheRes .loadErr = err
212
267
if err != nil {
0 commit comments