@@ -6,32 +6,43 @@ import (
6
6
"go/parser"
7
7
"go/token"
8
8
"path/filepath"
9
+ "regexp"
9
10
"strings"
10
11
11
12
"github.com/golangci/golangci-lint/pkg/logutils"
12
13
"github.com/golangci/golangci-lint/pkg/result"
13
14
)
14
15
15
- var autogenDebugf = logutils .Debug (logutils .DebugKeyAutogenExclude )
16
+ const (
17
+ genCodeGenerated = "code generated"
18
+ genDoNotEdit = "do not edit"
19
+ genAutoFile = "autogenerated file" // easyjson
20
+ )
16
21
17
- type ageFileSummary struct {
18
- isGenerated bool
19
- }
22
+ var _ Processor = & AutogeneratedExclude {}
20
23
21
- type ageFileSummaryCache map [string ]* ageFileSummary
24
+ type fileSummary struct {
25
+ generated bool
26
+ }
22
27
23
28
type AutogeneratedExclude struct {
24
- fileSummaryCache ageFileSummaryCache
29
+ debugf logutils.DebugFunc
30
+
31
+ strict bool
32
+ strictPattern * regexp.Regexp
33
+
34
+ fileSummaryCache map [string ]* fileSummary
25
35
}
26
36
27
- func NewAutogeneratedExclude () * AutogeneratedExclude {
37
+ func NewAutogeneratedExclude (strict bool ) * AutogeneratedExclude {
28
38
return & AutogeneratedExclude {
29
- fileSummaryCache : ageFileSummaryCache {},
39
+ debugf : logutils .Debug (logutils .DebugKeyAutogenExclude ),
40
+ strict : strict ,
41
+ strictPattern : regexp .MustCompile (`^// Code generated .* DO NOT EDIT\.$` ),
42
+ fileSummaryCache : map [string ]* fileSummary {},
30
43
}
31
44
}
32
45
33
- var _ Processor = & AutogeneratedExclude {}
34
-
35
46
func (p * AutogeneratedExclude ) Name () string {
36
47
return "autogenerated_exclude"
37
48
}
@@ -40,11 +51,7 @@ func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, e
40
51
return filterIssuesErr (issues , p .shouldPassIssue )
41
52
}
42
53
43
- func isSpecialAutogeneratedFile (filePath string ) bool {
44
- fileName := filepath .Base (filePath )
45
- // fake files or generation definitions to which //line points to for generated files
46
- return filepath .Ext (fileName ) != ".go"
47
- }
54
+ func (p * AutogeneratedExclude ) Finish () {}
48
55
49
56
func (p * AutogeneratedExclude ) shouldPassIssue (issue * result.Issue ) (bool , error ) {
50
57
if issue .FromLinter == "typecheck" {
@@ -56,66 +63,96 @@ func (p *AutogeneratedExclude) shouldPassIssue(issue *result.Issue) (bool, error
56
63
return true , nil
57
64
}
58
65
59
- if isSpecialAutogeneratedFile (issue .FilePath ()) {
66
+ if ! isGoFile (issue .FilePath ()) {
60
67
return false , nil
61
68
}
62
69
63
- fs , err := p .getOrCreateFileSummary (issue )
64
- if err != nil {
65
- return false , err
70
+ // The file is already known.
71
+ fs := p .fileSummaryCache [issue .FilePath ()]
72
+ if fs != nil {
73
+ return ! fs .generated , nil
66
74
}
67
75
76
+ fs = & fileSummary {}
77
+ p .fileSummaryCache [issue .FilePath ()] = fs
78
+
79
+ if issue .FilePath () == "" {
80
+ return false , errors .New ("no file path for issue" )
81
+ }
82
+
83
+ if p .strict {
84
+ var err error
85
+ fs .generated , err = p .isGeneratedFileStrict (issue .FilePath ())
86
+ if err != nil {
87
+ return false , fmt .Errorf ("failed to get doc of file %s: %w" , issue .FilePath (), err )
88
+ }
89
+ } else {
90
+ doc , err := getComments (issue .FilePath ())
91
+ if err != nil {
92
+ return false , fmt .Errorf ("failed to get doc of file %s: %w" , issue .FilePath (), err )
93
+ }
94
+
95
+ fs .generated = p .isGeneratedFileLax (doc )
96
+ }
97
+
98
+ p .debugf ("file %q is generated: %t" , issue .FilePath (), fs .generated )
99
+
68
100
// don't report issues for autogenerated files
69
- return ! fs .isGenerated , nil
101
+ return ! fs .generated , nil
70
102
}
71
103
72
- // isGenerated reports whether the source file is generated code.
73
- // Using a bit laxer rules than https://go.dev/s/generatedcode to
74
- // match more generated code. See #48 and #72.
75
- func isGeneratedFileByComment (doc string ) bool {
76
- const (
77
- genCodeGenerated = "code generated"
78
- genDoNotEdit = "do not edit"
79
- genAutoFile = "autogenerated file" // easyjson
80
- )
81
-
104
+ // isGeneratedFileLax reports whether the source file is generated code.
105
+ // Using a bit laxer rules than https://go.dev/s/generatedcode to match more generated code.
106
+ // See https://github.com/golangci/golangci-lint/issues/48 and https://github.com/golangci/golangci-lint/issues/72.
107
+ func (p * AutogeneratedExclude ) isGeneratedFileLax (doc string ) bool {
82
108
markers := []string {genCodeGenerated , genDoNotEdit , genAutoFile }
109
+
83
110
doc = strings .ToLower (doc )
111
+
84
112
for _ , marker := range markers {
85
113
if strings .Contains (doc , marker ) {
86
- autogenDebugf ("doc contains marker %q: file is generated" , marker )
114
+ p .debugf ("doc contains marker %q: file is generated" , marker )
115
+
87
116
return true
88
117
}
89
118
}
90
119
91
- autogenDebugf ("doc of len %d doesn't contain any of markers: %s" , len (doc ), markers )
120
+ p .debugf ("doc of len %d doesn't contain any of markers: %s" , len (doc ), markers )
121
+
92
122
return false
93
123
}
94
124
95
- func (p * AutogeneratedExclude ) getOrCreateFileSummary (issue * result.Issue ) (* ageFileSummary , error ) {
96
- fs := p .fileSummaryCache [issue .FilePath ()]
97
- if fs != nil {
98
- return fs , nil
125
+ // Based on https://go.dev/s/generatedcode
126
+ // > This line must appear before the first non-comment, non-blank text in the file.
127
+ func (p * AutogeneratedExclude ) isGeneratedFileStrict (filePath string ) (bool , error ) {
128
+ file , err := parser .ParseFile (token .NewFileSet (), filePath , nil , parser .PackageClauseOnly | parser .ParseComments )
129
+ if err != nil {
130
+ return false , fmt .Errorf ("failed to parse file: %w" , err )
99
131
}
100
132
101
- fs = & ageFileSummary {}
102
- p .fileSummaryCache [issue .FilePath ()] = fs
103
-
104
- if issue .FilePath () == "" {
105
- return nil , errors .New ("no file path for issue" )
133
+ if file == nil || len (file .Comments ) == 0 {
134
+ return false , nil
106
135
}
107
136
108
- doc , err := getDoc (issue .FilePath ())
109
- if err != nil {
110
- return nil , fmt .Errorf ("failed to get doc of file %s: %w" , issue .FilePath (), err )
137
+ for _ , comment := range file .Comments {
138
+ if comment .Pos () > file .Package {
139
+ return false , nil
140
+ }
141
+
142
+ for _ , line := range comment .List {
143
+ generated := p .strictPattern .MatchString (line .Text )
144
+ if generated {
145
+ p .debugf ("doc contains ignore expression: file is generated" )
146
+
147
+ return true , nil
148
+ }
149
+ }
111
150
}
112
151
113
- fs .isGenerated = isGeneratedFileByComment (doc )
114
- autogenDebugf ("file %q is generated: %t" , issue .FilePath (), fs .isGenerated )
115
- return fs , nil
152
+ return false , nil
116
153
}
117
154
118
- func getDoc (filePath string ) (string , error ) {
155
+ func getComments (filePath string ) (string , error ) {
119
156
fset := token .NewFileSet ()
120
157
syntax , err := parser .ParseFile (fset , filePath , nil , parser .PackageClauseOnly | parser .ParseComments )
121
158
if err != nil {
@@ -130,4 +167,6 @@ func getDoc(filePath string) (string, error) {
130
167
return strings .Join (docLines , "\n " ), nil
131
168
}
132
169
133
- func (p * AutogeneratedExclude ) Finish () {}
170
+ func isGoFile (name string ) bool {
171
+ return filepath .Ext (name ) == ".go"
172
+ }
0 commit comments