github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/result/processors/autogenerated_exclude.go (about) 1 package processors 2 3 import ( 4 "errors" 5 "fmt" 6 "go/parser" 7 "go/token" 8 "path/filepath" 9 "strings" 10 11 "github.com/vanstinator/golangci-lint/pkg/logutils" 12 "github.com/vanstinator/golangci-lint/pkg/result" 13 ) 14 15 var autogenDebugf = logutils.Debug(logutils.DebugKeyAutogenExclude) 16 17 type ageFileSummary struct { 18 isGenerated bool 19 } 20 21 type ageFileSummaryCache map[string]*ageFileSummary 22 23 type AutogeneratedExclude struct { 24 fileSummaryCache ageFileSummaryCache 25 } 26 27 func NewAutogeneratedExclude() *AutogeneratedExclude { 28 return &AutogeneratedExclude{ 29 fileSummaryCache: ageFileSummaryCache{}, 30 } 31 } 32 33 var _ Processor = &AutogeneratedExclude{} 34 35 func (p *AutogeneratedExclude) Name() string { 36 return "autogenerated_exclude" 37 } 38 39 func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, error) { 40 return filterIssuesErr(issues, p.shouldPassIssue) 41 } 42 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 } 48 49 func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) { 50 if i.FromLinter == "typecheck" { 51 // don't hide typechecking errors in generated files: users expect to see why the project isn't compiling 52 return true, nil 53 } 54 55 if filepath.Base(i.FilePath()) == "go.mod" { 56 return true, nil 57 } 58 59 if isSpecialAutogeneratedFile(i.FilePath()) { 60 return false, nil 61 } 62 63 fs, err := p.getOrCreateFileSummary(i) 64 if err != nil { 65 return false, err 66 } 67 68 // don't report issues for autogenerated files 69 return !fs.isGenerated, nil 70 } 71 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 82 markers := []string{genCodeGenerated, genDoNotEdit, genAutoFile} 83 doc = strings.ToLower(doc) 84 for _, marker := range markers { 85 if strings.Contains(doc, marker) { 86 autogenDebugf("doc contains marker %q: file is generated", marker) 87 return true 88 } 89 } 90 91 autogenDebugf("doc of len %d doesn't contain any of markers: %s", len(doc), markers) 92 return false 93 } 94 95 func (p *AutogeneratedExclude) getOrCreateFileSummary(i *result.Issue) (*ageFileSummary, error) { 96 fs := p.fileSummaryCache[i.FilePath()] 97 if fs != nil { 98 return fs, nil 99 } 100 101 fs = &ageFileSummary{} 102 p.fileSummaryCache[i.FilePath()] = fs 103 104 if i.FilePath() == "" { 105 return nil, errors.New("no file path for issue") 106 } 107 108 doc, err := getDoc(i.FilePath()) 109 if err != nil { 110 return nil, fmt.Errorf("failed to get doc of file %s: %w", i.FilePath(), err) 111 } 112 113 fs.isGenerated = isGeneratedFileByComment(doc) 114 autogenDebugf("file %q is generated: %t", i.FilePath(), fs.isGenerated) 115 return fs, nil 116 } 117 118 func getDoc(filePath string) (string, error) { 119 fset := token.NewFileSet() 120 syntax, err := parser.ParseFile(fset, filePath, nil, parser.PackageClauseOnly|parser.ParseComments) 121 if err != nil { 122 return "", fmt.Errorf("failed to parse file: %w", err) 123 } 124 125 var docLines []string 126 for _, c := range syntax.Comments { 127 docLines = append(docLines, strings.TrimSpace(c.Text())) 128 } 129 130 return strings.Join(docLines, "\n"), nil 131 } 132 133 func (p *AutogeneratedExclude) Finish() {}