@@ -8,18 +8,25 @@ import (
8
8
"regexp"
9
9
"strings"
10
10
"unicode"
11
+
12
+ "github.com/golangci/golangci-lint/pkg/result"
11
13
)
12
14
13
15
type BaseIssue struct {
14
16
fullDirective string
15
17
directiveWithOptionalLeadingSpace string
16
18
position token.Position
19
+ replacement * result.Replacement
17
20
}
18
21
19
22
func (b BaseIssue ) Position () token.Position {
20
23
return b .position
21
24
}
22
25
26
+ func (b BaseIssue ) Replacement () * result.Replacement {
27
+ return b .replacement
28
+ }
29
+
23
30
type ExtraLeadingSpace struct {
24
31
BaseIssue
25
32
}
@@ -85,7 +92,7 @@ type UnusedCandidate struct {
85
92
func (i UnusedCandidate ) Details () string {
86
93
details := fmt .Sprintf ("directive `%s` is unused" , i .fullDirective )
87
94
if i .ExpectedLinter != "" {
88
- details += fmt .Sprintf (" for linter %s " , i .ExpectedLinter )
95
+ details += fmt .Sprintf (" for linter %q " , i .ExpectedLinter )
89
96
}
90
97
return details
91
98
}
@@ -100,6 +107,7 @@ type Issue interface {
100
107
Details () string
101
108
Position () token.Position
102
109
String () string
110
+ Replacement () * result.Replacement
103
111
}
104
112
105
113
type Needs uint
@@ -115,7 +123,7 @@ const (
115
123
var commentPattern = regexp .MustCompile (`^//\s*(nolint)(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\b` )
116
124
117
125
// matches a complete nolint directive
118
- var fullDirectivePattern = regexp .MustCompile (`^//\s*nolint(: \s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\s*(//.*)?\s*\n?$` )
126
+ var fullDirectivePattern = regexp .MustCompile (`^//\s*nolint(?::( \s*[\w-]+\s*(?:,\s*[\w-]+\s*)*) )?\s*(//.*)?\s*\n?$` )
119
127
120
128
type Linter struct {
121
129
excludes []string // lists individual linters that don't require explanations
@@ -143,96 +151,139 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
143
151
var issues []Issue
144
152
145
153
for _ , node := range nodes {
146
- if file , ok := node .(* ast.File ); ok {
147
- for _ , c := range file .Comments {
148
- for _ , comment := range c .List {
149
- if ! commentPattern .MatchString (comment .Text ) {
150
- continue
151
- }
154
+ file , ok := node .(* ast.File )
155
+ if ! ok {
156
+ continue
157
+ }
152
158
153
- // check for a space between the "//" and the directive
154
- leadingSpaceMatches := leadingSpacePattern .FindStringSubmatch (comment .Text )
159
+ for _ , c := range file .Comments {
160
+ for _ , comment := range c .List {
161
+ if ! commentPattern .MatchString (comment .Text ) {
162
+ continue
163
+ }
155
164
156
- var leadingSpace string
157
- if len (leadingSpaceMatches ) > 0 {
158
- leadingSpace = leadingSpaceMatches [1 ]
159
- }
165
+ // check for a space between the "//" and the directive
166
+ leadingSpaceMatches := leadingSpacePattern .FindStringSubmatch (comment .Text )
160
167
161
- directiveWithOptionalLeadingSpace := comment .Text
162
- if len (leadingSpace ) > 0 {
163
- split := strings .Split (strings .SplitN (comment .Text , ":" , 2 )[0 ], "//" )
164
- directiveWithOptionalLeadingSpace = "// " + strings .TrimSpace (split [1 ])
165
- }
168
+ var leadingSpace string
169
+ if len (leadingSpaceMatches ) > 0 {
170
+ leadingSpace = leadingSpaceMatches [1 ]
171
+ }
166
172
167
- base := BaseIssue {
168
- fullDirective : comment . Text ,
169
- directiveWithOptionalLeadingSpace : directiveWithOptionalLeadingSpace ,
170
- position : fset . Position ( comment . Pos ()),
171
- }
173
+ directiveWithOptionalLeadingSpace := comment . Text
174
+ if len ( leadingSpace ) > 0 {
175
+ split := strings . Split ( strings . SplitN ( comment . Text , ":" , 2 )[ 0 ], "//" )
176
+ directiveWithOptionalLeadingSpace = "// " + strings . TrimSpace ( split [ 1 ])
177
+ }
172
178
173
- // check for, report and eliminate leading spaces so we can check for other issues
174
- if len (leadingSpace ) > 1 {
175
- issues = append (issues , ExtraLeadingSpace {BaseIssue : base })
176
- }
179
+ pos := fset .Position (comment .Pos ())
180
+ end := fset .Position (comment .End ())
177
181
178
- if (l .needs & NeedsMachineOnly ) != 0 && len (leadingSpace ) > 0 {
179
- issues = append (issues , NotMachine {BaseIssue : base })
180
- }
182
+ base := BaseIssue {
183
+ fullDirective : comment .Text ,
184
+ directiveWithOptionalLeadingSpace : directiveWithOptionalLeadingSpace ,
185
+ position : pos ,
186
+ }
181
187
182
- fullMatches := fullDirectivePattern .FindStringSubmatch (comment .Text )
183
- if len (fullMatches ) == 0 {
184
- issues = append (issues , ParseError {BaseIssue : base })
185
- continue
188
+ // check for, report and eliminate leading spaces so we can check for other issues
189
+ if len (leadingSpace ) > 0 {
190
+ removeWhitespace := & result.Replacement {
191
+ Inline : & result.InlineFix {
192
+ StartCol : pos .Column + 1 ,
193
+ Length : len (leadingSpace ),
194
+ NewString : "" ,
195
+ },
196
+ }
197
+ if (l .needs & NeedsMachineOnly ) != 0 {
198
+ issue := NotMachine {BaseIssue : base }
199
+ issue .BaseIssue .replacement = removeWhitespace
200
+ issues = append (issues , issue )
201
+ } else if len (leadingSpace ) > 1 {
202
+ issue := ExtraLeadingSpace {BaseIssue : base }
203
+ issue .BaseIssue .replacement = removeWhitespace
204
+ issue .BaseIssue .replacement .Inline .NewString = " " // assume a single space was intended
205
+ issues = append (issues , issue )
186
206
}
207
+ }
187
208
188
- lintersText , explanation := fullMatches [1 ], fullMatches [2 ]
189
- var linters []string
190
- if len (lintersText ) > 0 {
191
- lls := strings .Split (lintersText [1 :], "," )
192
- linters = make ([]string , 0 , len (lls ))
193
- for _ , ll := range lls {
194
- ll = strings .TrimSpace (ll )
195
- if ll != "" {
196
- linters = append (linters , ll )
197
- }
209
+ fullMatches := fullDirectivePattern .FindStringSubmatch (comment .Text )
210
+ if len (fullMatches ) == 0 {
211
+ issues = append (issues , ParseError {BaseIssue : base })
212
+ continue
213
+ }
214
+
215
+ lintersText , explanation := fullMatches [1 ], fullMatches [2 ]
216
+ var linters []string
217
+ var linterRange []result.Range
218
+ if len (lintersText ) > 0 {
219
+ lls := strings .Split (lintersText , "," )
220
+ linters = make ([]string , 0 , len (lls ))
221
+ rangeStart := (pos .Column - 1 ) + len ("//" ) + len (leadingSpace ) + len ("nolint:" )
222
+ for i , ll := range lls {
223
+ rangeEnd := rangeStart + len (ll )
224
+ if i < len (lls )- 1 {
225
+ rangeEnd ++ // include trailing comma
198
226
}
227
+ trimmedLinterName := strings .TrimSpace (ll )
228
+ if trimmedLinterName != "" {
229
+ linters = append (linters , trimmedLinterName )
230
+ linterRange = append (linterRange , result.Range {From : rangeStart , To : rangeEnd })
231
+ }
232
+ rangeStart = rangeEnd
199
233
}
234
+ }
200
235
201
- if (l .needs & NeedsSpecific ) != 0 {
202
- if len (linters ) == 0 {
203
- issues = append (issues , NotSpecific {BaseIssue : base })
204
- }
236
+ if (l .needs & NeedsSpecific ) != 0 {
237
+ if len (linters ) == 0 {
238
+ issues = append (issues , NotSpecific {BaseIssue : base })
205
239
}
240
+ }
206
241
207
- // when detecting unused directives, we send all the directives through and filter them out in the nolint processor
208
- if (l .needs & NeedsUnused ) != 0 {
209
- if len (linters ) == 0 {
210
- issues = append (issues , UnusedCandidate {BaseIssue : base })
211
- } else {
212
- for _ , linter := range linters {
213
- issues = append (issues , UnusedCandidate {BaseIssue : base , ExpectedLinter : linter })
214
- }
215
- }
242
+ // when detecting unused directives, we send all the directives through and filter them out in the nolint processor
243
+ if (l .needs & NeedsUnused ) != 0 {
244
+ removeNolintCompletely := & result.Replacement {
245
+ Inline : & result.InlineFix {
246
+ StartCol : pos .Column - 1 ,
247
+ Length : end .Column - pos .Column ,
248
+ NewString : "" ,
249
+ },
216
250
}
217
251
218
- if (l .needs & NeedsExplanation ) != 0 && (explanation == "" || strings .TrimSpace (explanation ) == "//" ) {
219
- needsExplanation := len (linters ) == 0 // if no linters are mentioned, we must have explanation
220
- // otherwise, check if we are excluding all of the mentioned linters
221
- for _ , ll := range linters {
222
- if ! l .excludeByLinter [ll ] { // if a linter does require explanation
223
- needsExplanation = true
224
- break
252
+ if len (linters ) == 0 {
253
+ issue := UnusedCandidate {BaseIssue : base }
254
+ issue .replacement = removeNolintCompletely
255
+ issues = append (issues , issue )
256
+ } else {
257
+ for _ , linter := range linters {
258
+ issue := UnusedCandidate {BaseIssue : base , ExpectedLinter : linter }
259
+ // only offer replacement if there is a single linter
260
+ // because of issues around commas and the possibility of all
261
+ // linters being removed
262
+ if len (linters ) == 1 {
263
+ issue .replacement = removeNolintCompletely
225
264
}
265
+ issues = append (issues , issue )
226
266
}
267
+ }
268
+ }
227
269
228
- if needsExplanation {
229
- fullDirectiveWithoutExplanation := trailingBlankExplanation .ReplaceAllString (comment .Text , "" )
230
- issues = append (issues , NoExplanation {
231
- BaseIssue : base ,
232
- fullDirectiveWithoutExplanation : fullDirectiveWithoutExplanation ,
233
- })
270
+ if (l .needs & NeedsExplanation ) != 0 && (explanation == "" || strings .TrimSpace (explanation ) == "//" ) {
271
+ needsExplanation := len (linters ) == 0 // if no linters are mentioned, we must have explanation
272
+ // otherwise, check if we are excluding all of the mentioned linters
273
+ for _ , ll := range linters {
274
+ if ! l .excludeByLinter [ll ] { // if a linter does require explanation
275
+ needsExplanation = true
276
+ break
234
277
}
235
278
}
279
+
280
+ if needsExplanation {
281
+ fullDirectiveWithoutExplanation := trailingBlankExplanation .ReplaceAllString (comment .Text , "" )
282
+ issues = append (issues , NoExplanation {
283
+ BaseIssue : base ,
284
+ fullDirectiveWithoutExplanation : fullDirectiveWithoutExplanation ,
285
+ })
286
+ }
236
287
}
237
288
}
238
289
}
0 commit comments