1package processors 2 3import ( 4 "fmt" 5 "go/parser" 6 "go/token" 7 "path/filepath" 8 "strings" 9 10 "github.com/pkg/errors" 11 12 "github.com/golangci/golangci-lint/pkg/logutils" 13 "github.com/golangci/golangci-lint/pkg/result" 14) 15 16var autogenDebugf = logutils.Debug("autogen_exclude") 17 18type ageFileSummary struct { 19 isGenerated bool 20} 21 22type ageFileSummaryCache map[string]*ageFileSummary 23 24type AutogeneratedExclude struct { 25 fileSummaryCache ageFileSummaryCache 26} 27 28func NewAutogeneratedExclude() *AutogeneratedExclude { 29 return &AutogeneratedExclude{ 30 fileSummaryCache: ageFileSummaryCache{}, 31 } 32} 33 34var _ Processor = &AutogeneratedExclude{} 35 36func (p AutogeneratedExclude) Name() string { 37 return "autogenerated_exclude" 38} 39 40func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, error) { 41 return filterIssuesErr(issues, p.shouldPassIssue) 42} 43 44func isSpecialAutogeneratedFile(filePath string) bool { 45 fileName := filepath.Base(filePath) 46 // fake files or generation definitions to which //line points to for generated files 47 return filepath.Ext(fileName) != ".go" 48} 49 50func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) { 51 if i.FromLinter == "typecheck" { 52 // don't hide typechecking errors in generated files: users expect to see why the project isn't compiling 53 return true, nil 54 } 55 56 if filepath.Base(i.FilePath()) == "go.mod" { 57 return true, nil 58 } 59 60 if isSpecialAutogeneratedFile(i.FilePath()) { 61 return false, nil 62 } 63 64 fs, err := p.getOrCreateFileSummary(i) 65 if err != nil { 66 return false, err 67 } 68 69 // don't report issues for autogenerated files 70 return !fs.isGenerated, nil 71} 72 73// isGenerated reports whether the source file is generated code. 74// Using a bit laxer rules than https://golang.org/s/generatedcode to 75// match more generated code. See #48 and #72. 76func isGeneratedFileByComment(doc string) bool { 77 const ( 78 genCodeGenerated = "code generated" 79 genDoNotEdit = "do not edit" 80 genAutoFile = "autogenerated file" // easyjson 81 ) 82 83 markers := []string{genCodeGenerated, genDoNotEdit, genAutoFile} 84 doc = strings.ToLower(doc) 85 for _, marker := range markers { 86 if strings.Contains(doc, marker) { 87 autogenDebugf("doc contains marker %q: file is generated", marker) 88 return true 89 } 90 } 91 92 autogenDebugf("doc of len %d doesn't contain any of markers: %s", len(doc), markers) 93 return false 94} 95 96func (p *AutogeneratedExclude) getOrCreateFileSummary(i *result.Issue) (*ageFileSummary, error) { 97 fs := p.fileSummaryCache[i.FilePath()] 98 if fs != nil { 99 return fs, nil 100 } 101 102 fs = &ageFileSummary{} 103 p.fileSummaryCache[i.FilePath()] = fs 104 105 if i.FilePath() == "" { 106 return nil, fmt.Errorf("no file path for issue") 107 } 108 109 doc, err := getDoc(i.FilePath()) 110 if err != nil { 111 return nil, errors.Wrapf(err, "failed to get doc of file %s", i.FilePath()) 112 } 113 114 fs.isGenerated = isGeneratedFileByComment(doc) 115 autogenDebugf("file %q is generated: %t", i.FilePath(), fs.isGenerated) 116 return fs, nil 117} 118 119func getDoc(filePath string) (string, error) { 120 fset := token.NewFileSet() 121 syntax, err := parser.ParseFile(fset, filePath, nil, parser.PackageClauseOnly|parser.ParseComments) 122 if err != nil { 123 return "", errors.Wrap(err, "failed to parse file") 124 } 125 126 var docLines []string 127 for _, c := range syntax.Comments { 128 docLines = append(docLines, strings.TrimSpace(c.Text())) 129 } 130 131 return strings.Join(docLines, "\n"), nil 132} 133 134func (p AutogeneratedExclude) Finish() {} 135