@@ -6,32 +6,43 @@ import (
66 "go/parser"
77 "go/token"
88 "path/filepath"
9+ "regexp"
910 "strings"
1011
1112 "github.com/golangci/golangci-lint/pkg/logutils"
1213 "github.com/golangci/golangci-lint/pkg/result"
1314)
1415
15- var autogenDebugf = logutils .Debug (logutils .DebugKeyAutogenExclude )
16+ const (
17+ genCodeGenerated = "code generated"
18+ genDoNotEdit = "do not edit"
19+ genAutoFile = "autogenerated file" // easyjson
20+ )
1621
17- type ageFileSummary struct {
18- isGenerated bool
19- }
22+ var _ Processor = & AutogeneratedExclude {}
2023
21- type ageFileSummaryCache map [string ]* ageFileSummary
24+ type fileSummary struct {
25+ generated bool
26+ }
2227
2328type AutogeneratedExclude struct {
24- fileSummaryCache ageFileSummaryCache
29+ debugf logutils.DebugFunc
30+
31+ strict bool
32+ strictPattern * regexp.Regexp
33+
34+ fileSummaryCache map [string ]* fileSummary
2535}
2636
27- func NewAutogeneratedExclude () * AutogeneratedExclude {
37+ func NewAutogeneratedExclude (strict bool ) * AutogeneratedExclude {
2838 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 {},
3043 }
3144}
3245
33- var _ Processor = & AutogeneratedExclude {}
34-
3546func (p * AutogeneratedExclude ) Name () string {
3647 return "autogenerated_exclude"
3748}
@@ -40,11 +51,7 @@ func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, e
4051 return filterIssuesErr (issues , p .shouldPassIssue )
4152}
4253
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 () {}
4855
4956func (p * AutogeneratedExclude ) shouldPassIssue (issue * result.Issue ) (bool , error ) {
5057 if issue .FromLinter == "typecheck" {
@@ -56,66 +63,96 @@ func (p *AutogeneratedExclude) shouldPassIssue(issue *result.Issue) (bool, error
5663 return true , nil
5764 }
5865
59- if isSpecialAutogeneratedFile (issue .FilePath ()) {
66+ if ! isGoFile (issue .FilePath ()) {
6067 return false , nil
6168 }
6269
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
6674 }
6775
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+
68100 // don't report issues for autogenerated files
69- return ! fs .isGenerated , nil
101+ return ! fs .generated , nil
70102}
71103
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 {
82108 markers := []string {genCodeGenerated , genDoNotEdit , genAutoFile }
109+
83110 doc = strings .ToLower (doc )
111+
84112 for _ , marker := range markers {
85113 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+
87116 return true
88117 }
89118 }
90119
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+
92122 return false
93123}
94124
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 )
99131 }
100132
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
106135 }
107136
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+ }
111150 }
112151
113- fs .isGenerated = isGeneratedFileByComment (doc )
114- autogenDebugf ("file %q is generated: %t" , issue .FilePath (), fs .isGenerated )
115- return fs , nil
152+ return false , nil
116153}
117154
118- func getDoc (filePath string ) (string , error ) {
155+ func getComments (filePath string ) (string , error ) {
119156 fset := token .NewFileSet ()
120157 syntax , err := parser .ParseFile (fset , filePath , nil , parser .PackageClauseOnly | parser .ParseComments )
121158 if err != nil {
@@ -130,4 +167,6 @@ func getDoc(filePath string) (string, error) {
130167 return strings .Join (docLines , "\n " ), nil
131168}
132169
133- func (p * AutogeneratedExclude ) Finish () {}
170+ func isGoFile (name string ) bool {
171+ return filepath .Ext (name ) == ".go"
172+ }
0 commit comments