Skip to content

Commit e7c65cb

Browse files
committed
chore: refactor nolintlint
1 parent 19a66e6 commit e7c65cb

11 files changed

+305
-316
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"unicode"
7+
)
8+
9+
func formatExtraLeadingSpace(fullDirective string) string {
10+
return fmt.Sprintf("directive `%s` should not have more than one leading space", fullDirective)
11+
}
12+
13+
func formatNotMachine(fullDirective string) string {
14+
expected := fullDirective[:2] + strings.TrimLeftFunc(fullDirective[2:], unicode.IsSpace)
15+
return fmt.Sprintf("directive `%s` should be written without leading space as `%s`",
16+
fullDirective, expected)
17+
}
18+
19+
func formatNotSpecific(fullDirective, directiveWithOptionalLeadingSpace string) string {
20+
return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`",
21+
fullDirective, directiveWithOptionalLeadingSpace)
22+
}
23+
24+
func formatParseError(fullDirective, directiveWithOptionalLeadingSpace string) string {
25+
return fmt.Sprintf("directive `%s` should match `%s[:<comma-separated-linters>] [// <explanation>]`",
26+
fullDirective,
27+
directiveWithOptionalLeadingSpace)
28+
}
29+
30+
func formatNoExplanation(fullDirective, fullDirectiveWithoutExplanation string) string {
31+
return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`",
32+
fullDirective, fullDirectiveWithoutExplanation)
33+
}
34+
35+
func formatUnusedCandidate(fullDirective, expectedLinter string) string {
36+
details := fmt.Sprintf("directive `%s` is unused", fullDirective)
37+
if expectedLinter != "" {
38+
details += fmt.Sprintf(" for linter %q", expectedLinter)
39+
}
40+
return details
41+
}

pkg/golinters/nolintlint/internal/nolintlint.go

Lines changed: 77 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -2,123 +2,16 @@
22
package internal
33

44
import (
5-
"fmt"
6-
"go/ast"
7-
"go/token"
85
"regexp"
96
"strings"
10-
"unicode"
117

8+
"golang.org/x/tools/go/analysis"
9+
10+
"github.com/golangci/golangci-lint/pkg/goanalysis"
1211
"github.com/golangci/golangci-lint/pkg/result"
1312
)
1413

15-
type BaseIssue struct {
16-
fullDirective string
17-
directiveWithOptionalLeadingSpace string
18-
position token.Position
19-
replacement *result.Replacement
20-
}
21-
22-
//nolint:gocritic // TODO(ldez) must be change in the future.
23-
func (b BaseIssue) Position() token.Position {
24-
return b.position
25-
}
26-
27-
//nolint:gocritic // TODO(ldez) must be change in the future.
28-
func (b BaseIssue) Replacement() *result.Replacement {
29-
return b.replacement
30-
}
31-
32-
type ExtraLeadingSpace struct {
33-
BaseIssue
34-
}
35-
36-
//nolint:gocritic // TODO(ldez) must be change in the future.
37-
func (i ExtraLeadingSpace) Details() string {
38-
return fmt.Sprintf("directive `%s` should not have more than one leading space", i.fullDirective)
39-
}
40-
41-
func (i ExtraLeadingSpace) String() string { return toString(i) }
42-
43-
type NotMachine struct {
44-
BaseIssue
45-
}
46-
47-
//nolint:gocritic // TODO(ldez) must be change in the future.
48-
func (i NotMachine) Details() string {
49-
expected := i.fullDirective[:2] + strings.TrimLeftFunc(i.fullDirective[2:], unicode.IsSpace)
50-
return fmt.Sprintf("directive `%s` should be written without leading space as `%s`",
51-
i.fullDirective, expected)
52-
}
53-
54-
func (i NotMachine) String() string { return toString(i) }
55-
56-
type NotSpecific struct {
57-
BaseIssue
58-
}
59-
60-
//nolint:gocritic // TODO(ldez) must be change in the future.
61-
func (i NotSpecific) Details() string {
62-
return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`",
63-
i.fullDirective, i.directiveWithOptionalLeadingSpace)
64-
}
65-
66-
func (i NotSpecific) String() string { return toString(i) }
67-
68-
type ParseError struct {
69-
BaseIssue
70-
}
71-
72-
//nolint:gocritic // TODO(ldez) must be change in the future.
73-
func (i ParseError) Details() string {
74-
return fmt.Sprintf("directive `%s` should match `%s[:<comma-separated-linters>] [// <explanation>]`",
75-
i.fullDirective,
76-
i.directiveWithOptionalLeadingSpace)
77-
}
78-
79-
func (i ParseError) String() string { return toString(i) }
80-
81-
type NoExplanation struct {
82-
BaseIssue
83-
fullDirectiveWithoutExplanation string
84-
}
85-
86-
//nolint:gocritic // TODO(ldez) must be change in the future.
87-
func (i NoExplanation) Details() string {
88-
return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`",
89-
i.fullDirective, i.fullDirectiveWithoutExplanation)
90-
}
91-
92-
func (i NoExplanation) String() string { return toString(i) }
93-
94-
type UnusedCandidate struct {
95-
BaseIssue
96-
ExpectedLinter string
97-
}
98-
99-
//nolint:gocritic // TODO(ldez) must be change in the future.
100-
func (i UnusedCandidate) Details() string {
101-
details := fmt.Sprintf("directive `%s` is unused", i.fullDirective)
102-
if i.ExpectedLinter != "" {
103-
details += fmt.Sprintf(" for linter %q", i.ExpectedLinter)
104-
}
105-
return details
106-
}
107-
108-
func (i UnusedCandidate) String() string { return toString(i) }
109-
110-
func toString(issue Issue) string {
111-
return fmt.Sprintf("%s at %s", issue.Details(), issue.Position())
112-
}
113-
114-
type Issue interface {
115-
Details() string
116-
Position() token.Position
117-
String() string
118-
Replacement() *result.Replacement
119-
}
120-
121-
type Needs uint
14+
const LinterName = "nolintlint"
12215

12316
const (
12417
NeedsMachineOnly Needs = 1 << iota
@@ -128,6 +21,8 @@ const (
12821
NeedsAll = NeedsMachineOnly | NeedsSpecific | NeedsExplanation
12922
)
13023

24+
type Needs uint
25+
13126
var commentPattern = regexp.MustCompile(`^//\s*(nolint)(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\b`)
13227

13328
// matches a complete nolint directive
@@ -157,15 +52,10 @@ var (
15752
)
15853

15954
//nolint:funlen,gocyclo // the function is going to be refactored in the future
160-
func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
161-
var issues []Issue
162-
163-
for _, node := range nodes {
164-
file, ok := node.(*ast.File)
165-
if !ok {
166-
continue
167-
}
55+
func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) {
56+
var issues []goanalysis.Issue
16857

58+
for _, file := range pass.Files {
16959
for _, c := range file.Comments {
17060
for _, comment := range c.List {
17161
if !commentPattern.MatchString(comment.Text) {
@@ -188,39 +78,51 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
18878
split := strings.Split(strings.SplitN(comment.Text, ":", 2)[0], "//")
18979
directiveWithOptionalLeadingSpace += strings.TrimSpace(split[1])
19080

191-
pos := fset.Position(comment.Pos())
192-
end := fset.Position(comment.End())
193-
194-
base := BaseIssue{
195-
fullDirective: comment.Text,
196-
directiveWithOptionalLeadingSpace: directiveWithOptionalLeadingSpace,
197-
position: pos,
198-
}
81+
pos := pass.Fset.Position(comment.Pos())
82+
end := pass.Fset.Position(comment.End())
19983

20084
// check for, report and eliminate leading spaces, so we can check for other issues
20185
if leadingSpace != "" {
20286
removeWhitespace := &result.Replacement{
20387
Inline: &result.InlineFix{
204-
StartCol: pos.Column + 1,
205-
Length: len(leadingSpace),
206-
NewString: "",
88+
StartCol: pos.Column + 1,
89+
Length: len(leadingSpace),
20790
},
20891
}
92+
20993
if (l.needs & NeedsMachineOnly) != 0 {
210-
issue := NotMachine{BaseIssue: base}
211-
issue.BaseIssue.replacement = removeWhitespace
212-
issues = append(issues, issue)
94+
issue := &result.Issue{
95+
FromLinter: LinterName,
96+
Text: formatNotMachine(comment.Text),
97+
Pos: pos,
98+
Replacement: removeWhitespace,
99+
}
100+
101+
issues = append(issues, goanalysis.NewIssue(issue, pass))
213102
} else if len(leadingSpace) > 1 {
214-
issue := ExtraLeadingSpace{BaseIssue: base}
215-
issue.BaseIssue.replacement = removeWhitespace
216-
issue.BaseIssue.replacement.Inline.NewString = " " // assume a single space was intended
217-
issues = append(issues, issue)
103+
issue := &result.Issue{
104+
FromLinter: LinterName,
105+
Text: formatExtraLeadingSpace(comment.Text),
106+
Pos: pos,
107+
Replacement: removeWhitespace,
108+
}
109+
110+
issue.Replacement.Inline.NewString = " " // assume a single space was intended
111+
112+
issues = append(issues, goanalysis.NewIssue(issue, pass))
218113
}
219114
}
220115

221116
fullMatches := fullDirectivePattern.FindStringSubmatch(comment.Text)
222117
if len(fullMatches) == 0 {
223-
issues = append(issues, ParseError{BaseIssue: base})
118+
issue := &result.Issue{
119+
FromLinter: LinterName,
120+
Text: formatParseError(comment.Text, directiveWithOptionalLeadingSpace),
121+
Pos: pos,
122+
}
123+
124+
issues = append(issues, goanalysis.NewIssue(issue, pass))
125+
224126
continue
225127
}
226128

@@ -246,7 +148,13 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
246148

247149
if (l.needs & NeedsSpecific) != 0 {
248150
if len(linters) == 0 {
249-
issues = append(issues, NotSpecific{BaseIssue: base})
151+
issue := &result.Issue{
152+
FromLinter: LinterName,
153+
Text: formatNotSpecific(comment.Text, directiveWithOptionalLeadingSpace),
154+
Pos: pos,
155+
}
156+
157+
issues = append(issues, goanalysis.NewIssue(issue, pass))
250158
}
251159
}
252160

@@ -261,26 +169,39 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
261169
removeNolintCompletely.NeedOnlyDelete = true
262170
} else {
263171
removeNolintCompletely.Inline = &result.InlineFix{
264-
StartCol: startCol,
265-
Length: end.Column - pos.Column,
266-
NewString: "",
172+
StartCol: startCol,
173+
Length: end.Column - pos.Column,
267174
}
268175
}
269176

270177
if len(linters) == 0 {
271-
issue := UnusedCandidate{BaseIssue: base}
272-
issue.replacement = removeNolintCompletely
273-
issues = append(issues, issue)
178+
issue := &result.Issue{
179+
FromLinter: LinterName,
180+
Text: formatUnusedCandidate(comment.Text, ""),
181+
Pos: pos,
182+
ExpectNoLint: true,
183+
Replacement: removeNolintCompletely,
184+
}
185+
186+
issues = append(issues, goanalysis.NewIssue(issue, pass))
274187
} else {
275188
for _, linter := range linters {
276-
issue := UnusedCandidate{BaseIssue: base, ExpectedLinter: linter}
189+
issue := &result.Issue{
190+
FromLinter: LinterName,
191+
Text: formatUnusedCandidate(comment.Text, linter),
192+
Pos: pos,
193+
ExpectNoLint: true,
194+
ExpectedNoLintLinter: linter,
195+
}
196+
277197
// only offer replacement if there is a single linter
278198
// because of issues around commas and the possibility of all
279199
// linters being removed
280200
if len(linters) == 1 {
281-
issue.replacement = removeNolintCompletely
201+
issue.Replacement = removeNolintCompletely
282202
}
283-
issues = append(issues, issue)
203+
204+
issues = append(issues, goanalysis.NewIssue(issue, pass))
284205
}
285206
}
286207
}
@@ -297,10 +218,14 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
297218

298219
if needsExplanation {
299220
fullDirectiveWithoutExplanation := trailingBlankExplanation.ReplaceAllString(comment.Text, "")
300-
issues = append(issues, NoExplanation{
301-
BaseIssue: base,
302-
fullDirectiveWithoutExplanation: fullDirectiveWithoutExplanation,
303-
})
221+
222+
issue := &result.Issue{
223+
FromLinter: LinterName,
224+
Text: formatNoExplanation(comment.Text, fullDirectiveWithoutExplanation),
225+
Pos: pos,
226+
}
227+
228+
issues = append(issues, goanalysis.NewIssue(issue, pass))
304229
}
305230
}
306231
}

0 commit comments

Comments
 (0)