Skip to content

Commit 7e85d7d

Browse files
committed
Allow multiple linters to be removed from nolint statement by nolint fixer
Also ensure that //nolint:nolintlint can be used to disable nolintlint
1 parent aeb9830 commit 7e85d7d

File tree

7 files changed

+115
-51
lines changed

7 files changed

+115
-51
lines changed

pkg/golinters/nolintlint/nolintlint.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,22 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
208208

209209
lintersText, explanation := fullMatches[1], fullMatches[2]
210210
var linters []string
211+
var linterRange []result.Range
211212
if len(lintersText) > 0 {
212213
lls := strings.Split(lintersText, ",")
213214
linters = make([]string, 0, len(lls))
214-
for _, ll := range lls {
215-
ll = strings.TrimSpace(ll)
216-
if ll != "" {
217-
linters = append(linters, ll)
215+
rangeStart := (pos.Column - 1) + len("//") + len(leadingSpace) + len("nolint:")
216+
for i, ll := range lls {
217+
rangeEnd := rangeStart + len(ll)
218+
if i < len(lls) - 1 {
219+
rangeEnd++ // include trailing comma
218220
}
221+
trimmedLinterName := strings.TrimSpace(ll)
222+
if trimmedLinterName != "" {
223+
linters = append(linters, trimmedLinterName)
224+
linterRange = append(linterRange, result.Range{From: rangeStart, To: rangeEnd})
225+
}
226+
rangeStart = rangeEnd
219227
}
220228
}
221229

@@ -244,12 +252,11 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
244252
issue := UnusedCandidate{BaseIssue: base, ExpectedLinter: linter}
245253
replacement := removeNolintCompletely
246254
if len(linters) > 1 {
247-
otherLinters := append(append([]string(nil), linters[0:i]...), linters[i+1:]...)
248255
replacement = &result.Replacement{
249256
Inline: &result.InlineFix{
250-
StartCol: (pos.Column - 1) + len("//") + len(leadingSpace) + len("nolint:"),
251-
Length: len(lintersText) - 1,
252-
NewString: strings.Join(otherLinters, ","),
257+
StartCol: linterRange[i].From,
258+
Length: linterRange[i].To - linterRange[i].From,
259+
NewString: "",
253260
},
254261
}
255262
}

pkg/golinters/nolintlint/nolintlint_test.go

+41-9
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func foo() {
202202
},
203203
},
204204
{
205-
desc: "needs unused with multiple specific linter generates replacement for each linter",
205+
desc: "needs unused with multiple specific linters generates a replacement for each linter",
206206
needs: NeedsUnused,
207207
contents: `
208208
package bar
@@ -216,28 +216,60 @@ func foo() {
216216
&result.Replacement{
217217
Inline: &result.InlineFix{
218218
StartCol: 17,
219-
Length: 22,
220-
NewString: "linter2,linter3",
219+
Length: 8,
220+
NewString: "",
221221
},
222222
},
223223
},
224224
{
225225
"directive `//nolint:linter1,linter2,linter3` is unused for linter \"linter2\" at testing.go:5:9",
226226
&result.Replacement{
227227
Inline: &result.InlineFix{
228-
StartCol: 17,
229-
Length: 22,
230-
NewString: "linter1,linter3",
228+
StartCol: 25,
229+
Length: 8,
230+
NewString: "",
231231
},
232232
},
233233
},
234234
{
235235
"directive `//nolint:linter1,linter2,linter3` is unused for linter \"linter3\" at testing.go:5:9",
236236
&result.Replacement{
237237
Inline: &result.InlineFix{
238-
StartCol: 17,
239-
Length: 22,
240-
NewString: "linter1,linter2",
238+
StartCol: 33,
239+
Length: 7,
240+
NewString: "",
241+
},
242+
},
243+
},
244+
},
245+
},
246+
{
247+
desc: "needs unused with multiple specific linters generates a replacement preserving space after commas",
248+
needs: NeedsUnused,
249+
contents: `
250+
package bar
251+
252+
func foo() {
253+
good() //nolint:linter1, linter2
254+
}`,
255+
expected: []issueWithReplacement{
256+
{
257+
"directive `//nolint:linter1, linter2` is unused for linter \"linter1\" at testing.go:5:10",
258+
&result.Replacement{
259+
Inline: &result.InlineFix{
260+
StartCol: 18,
261+
Length: 8,
262+
NewString: "",
263+
},
264+
},
265+
},
266+
{
267+
"directive `//nolint:linter1, linter2` is unused for linter \"linter2\" at testing.go:5:10",
268+
&result.Replacement{
269+
Inline: &result.InlineFix{
270+
StartCol: 26,
271+
Length: 8,
272+
NewString: "",
241273
},
242274
},
243275
},

pkg/result/processors/nolint.go

+21-22
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,27 @@ func (i *ignoredRange) doesMatch(issue *result.Issue) bool {
3030
return false
3131
}
3232

33-
// handle possible unused nolint directives
34-
// nolintlint generates potential issues for every nolint directive and they are filtered out here
35-
if issue.ExpectNoLint {
36-
if issue.ExpectedNoLintLinter != "" {
37-
return i.matchedIssueFromLinter[issue.ExpectedNoLintLinter]
33+
// only allow selective nolinting of nolintlint
34+
nolintFoundForLinter := len(i.linters) == 0 && issue.FromLinter != golinters.NolintlintName
35+
36+
for _, linterName := range i.linters {
37+
if linterName == issue.FromLinter {
38+
nolintFoundForLinter = true
39+
break
3840
}
39-
return len(i.matchedIssueFromLinter) > 0
4041
}
4142

42-
if len(i.linters) == 0 {
43+
if nolintFoundForLinter {
4344
return true
4445
}
4546

46-
for _, linterName := range i.linters {
47-
if linterName == issue.FromLinter {
48-
return true
47+
// handle possible unused nolint directives
48+
// nolintlint generates potential issues for every nolint directive and they are filtered out here
49+
if issue.FromLinter == golinters.NolintlintName && issue.ExpectNoLint {
50+
if issue.ExpectedNoLintLinter != "" {
51+
return i.matchedIssueFromLinter[issue.ExpectedNoLintLinter]
4952
}
53+
return len(i.matchedIssueFromLinter) > 0
5054
}
5155

5256
return false
@@ -142,19 +146,14 @@ func (p *Nolint) buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, fil
142146

143147
func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
144148
nolintDebugf("got issue: %v", *i)
145-
if i.FromLinter == golinters.NolintlintName {
146-
// always pass nolintlint issues except ones trying find unused nolint directives
147-
if !i.ExpectNoLint {
148-
return true, nil
149-
}
150-
if i.ExpectedNoLintLinter != "" {
151-
// don't expect disabled linters to cover their nolint statements
152-
nolintDebugf("enabled linters: %v", p.enabledLinters)
153-
if p.enabledLinters[i.ExpectedNoLintLinter] == nil {
154-
return false, nil
155-
}
156-
nolintDebugf("checking that lint issue was used for %s: %v", i.ExpectedNoLintLinter, i)
149+
150+
if i.FromLinter == golinters.NolintlintName && i.ExpectNoLint && i.ExpectedNoLintLinter != "" {
151+
// don't expect disabled linters to cover their nolint statements
152+
nolintDebugf("enabled linters: %v", p.enabledLinters)
153+
if p.enabledLinters[i.ExpectedNoLintLinter] == nil {
154+
return false, nil
157155
}
156+
nolintDebugf("checking that lint issue was used for %s: %v", i.ExpectedNoLintLinter, i)
158157
}
159158

160159
fd, err := p.getOrCreateFileData(i)

pkg/result/processors/nolint_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,31 @@ func TestNolintUnused(t *testing.T) {
291291
ExpectedNoLintLinter: "varcheck",
292292
}
293293

294+
// the issue below is another nolintlint issue that would be generated for the test file
295+
nolintlintIssueVarcheckUnusedOK := result.Issue{
296+
Pos: token.Position{
297+
Filename: fileName,
298+
Line: 5,
299+
},
300+
FromLinter: golinters.NolintlintName,
301+
ExpectNoLint: true,
302+
ExpectedNoLintLinter: "varcheck",
303+
}
304+
294305
t.Run("when an issue does not occur, it is not removed from the nolintlint issues", func(t *testing.T) {
295306
p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
296307
defer p.Finish()
297308

298309
processAssertSame(t, p, nolintlintIssueVarcheck)
299310
})
300311

312+
t.Run("when an issue does not occur but nolintlint is nolinted, it is removed from the nolintlint issues", func(t *testing.T) {
313+
p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
314+
defer p.Finish()
315+
316+
processAssertEmpty(t, p, nolintlintIssueVarcheckUnusedOK)
317+
})
318+
301319
t.Run("when an issue occurs, it is removed from the nolintlint issues", func(t *testing.T) {
302320
p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
303321
defer p.Finish()
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
package testdata
22

33
var nolintVarcheck int // nolint:varcheck
4+
5+
var nolintVarcheckUnusedOK int // nolint:varcheck,nolintlint

test/testdata/fix/in/nolintlint.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
//config: linters-settings.nolintlint.allow-leading-space=false
33
package p
44

5+
import "fmt"
6+
57
func nolintlint() {
6-
run() // nolint:bob // leading space should be dropped
7-
run() // nolint:bob // leading spaces should be dropped
8+
fmt.Println() // nolint:bob // leading space should be dropped
9+
fmt.Println() // nolint:bob // leading spaces should be dropped
810
// note that the next lines will retain trailing whitespace when fixed
9-
run() //nolint // nolint should be dropped
10-
run() //nolint:lll // nolint should be dropped
11-
run() //nolint:alice,lll,bob // enabled linter should be dropped
12-
run() //nolint:alice,lll,bob,nolintlint // enabled linter should not be dropped when nolintlint is nolinted
11+
fmt.Println() //nolint // nolint should be dropped
12+
fmt.Println() //nolint:lll // nolint should be dropped
13+
fmt.Println() //nolint:alice,lll,bob // enabled linter should be dropped
14+
fmt.Println() //nolint:alice,lll, bob // enabled linter should be dropped but whitespace preserved
15+
fmt.Println() //nolint:alice,lll,bob,nolintlint // enabled linter should not be dropped when nolintlint is nolinted
1316
}

test/testdata/fix/out/nolintlint.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
//config: linters-settings.nolintlint.allow-leading-space=false
33
package p
44

5+
import "fmt"
6+
57
func nolintlint() {
6-
run() //nolint:bob // leading space should be dropped
7-
run() //nolint:bob // leading spaces should be dropped
8+
fmt.Println() //nolint:bob // leading space should be dropped
9+
fmt.Println() //nolint:bob // leading spaces should be dropped
810
// note that the next lines will retain trailing whitespace when fixed
9-
run()
10-
run()
11-
run() //nolint:alice,bob // enabled linter should be dropped
12-
run() //nolint:alice,lll,bob,nolintlint // enabled linter should not be dropped when nolintlint is nolinted
11+
fmt.Println()
12+
fmt.Println()
13+
fmt.Println() //nolint:alice,bob // enabled linter should be dropped
14+
fmt.Println() //nolint:alice, bob // enabled linter should be dropped but whitespace preserved
15+
fmt.Println() //nolint:alice,lll,bob,nolintlint // enabled linter should not be dropped when nolintlint is nolinted
1316
}

0 commit comments

Comments
 (0)