github.com/ldez/golangci-lint@v1.10.1/pkg/result/processors/autogenerated_exclude.go (about)

     1  package processors
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"strings"
     8  
     9  	"github.com/golangci/golangci-lint/pkg/lint/astcache"
    10  	"github.com/golangci/golangci-lint/pkg/logutils"
    11  	"github.com/golangci/golangci-lint/pkg/result"
    12  )
    13  
    14  var autogenDebugf = logutils.Debug("autogen_exclude")
    15  
    16  type ageFileSummary struct {
    17  	isGenerated bool
    18  }
    19  
    20  type ageFileSummaryCache map[string]*ageFileSummary
    21  
    22  type AutogeneratedExclude struct {
    23  	fileSummaryCache ageFileSummaryCache
    24  	astCache         *astcache.Cache
    25  }
    26  
    27  func NewAutogeneratedExclude(astCache *astcache.Cache) *AutogeneratedExclude {
    28  	return &AutogeneratedExclude{
    29  		fileSummaryCache: ageFileSummaryCache{},
    30  		astCache:         astCache,
    31  	}
    32  }
    33  
    34  var _ Processor = &AutogeneratedExclude{}
    35  
    36  func (p AutogeneratedExclude) Name() string {
    37  	return "autogenerated_exclude"
    38  }
    39  
    40  func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, error) {
    41  	return filterIssuesErr(issues, p.shouldPassIssue)
    42  }
    43  
    44  func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) {
    45  	fs, err := p.getOrCreateFileSummary(i)
    46  	if err != nil {
    47  		return false, err
    48  	}
    49  
    50  	// don't report issues for autogenerated files
    51  	return !fs.isGenerated, nil
    52  }
    53  
    54  // isGenerated reports whether the source file is generated code.
    55  // Using a bit laxer rules than https://golang.org/s/generatedcode to
    56  // match more generated code. See #48 and #72.
    57  func isGeneratedFileByComment(doc string) bool {
    58  	const (
    59  		genCodeGenerated = "code generated"
    60  		genDoNotEdit     = "do not edit"
    61  		genAutoFile      = "autogenerated file" // easyjson
    62  	)
    63  
    64  	markers := []string{genCodeGenerated, genDoNotEdit, genAutoFile}
    65  	doc = strings.ToLower(doc)
    66  	for _, marker := range markers {
    67  		if strings.Contains(doc, marker) {
    68  			autogenDebugf("doc contains marker %q: file is generated", marker)
    69  			return true
    70  		}
    71  	}
    72  
    73  	autogenDebugf("doc of len %d doesn't contain any of markers: %s", len(doc), markers)
    74  	return false
    75  }
    76  
    77  func (p *AutogeneratedExclude) getOrCreateFileSummary(i *result.Issue) (*ageFileSummary, error) {
    78  	fs := p.fileSummaryCache[i.FilePath()]
    79  	if fs != nil {
    80  		return fs, nil
    81  	}
    82  
    83  	fs = &ageFileSummary{}
    84  	p.fileSummaryCache[i.FilePath()] = fs
    85  
    86  	f := p.astCache.GetOrParse(i.FilePath())
    87  	if f.Err != nil {
    88  		return nil, fmt.Errorf("can't parse file %s: %s", i.FilePath(), f.Err)
    89  	}
    90  
    91  	autogenDebugf("file %q: astcache file is %+v", i.FilePath(), *f)
    92  
    93  	doc := getDoc(f.F, f.Fset, i.FilePath())
    94  
    95  	fs.isGenerated = isGeneratedFileByComment(doc)
    96  	autogenDebugf("file %q is generated: %t", i.FilePath(), fs.isGenerated)
    97  	return fs, nil
    98  }
    99  
   100  func getDoc(f *ast.File, fset *token.FileSet, filePath string) string {
   101  	// don't use just f.Doc: e.g. mockgen leaves extra line between comment and package name
   102  
   103  	var importPos token.Pos
   104  	if len(f.Imports) != 0 {
   105  		importPos = f.Imports[0].Pos()
   106  		autogenDebugf("file %q: search comments until first import pos %d (%s)",
   107  			filePath, importPos, fset.Position(importPos))
   108  	} else {
   109  		importPos = f.End()
   110  		autogenDebugf("file %q: search comments until EOF pos %d (%s)",
   111  			filePath, importPos, fset.Position(importPos))
   112  	}
   113  
   114  	var neededComments []string
   115  	for _, g := range f.Comments {
   116  		pos := g.Pos()
   117  		filePos := fset.Position(pos)
   118  		text := g.Text()
   119  
   120  		// files using cgo have implicitly added comment "Created by cgo - DO NOT EDIT" for go <= 1.10
   121  		// and "Code generated by cmd/cgo" for go >= 1.11
   122  		isCgoGenerated := strings.Contains(text, "Created by cgo") || strings.Contains(text, "Code generated by cmd/cgo")
   123  
   124  		isAllowed := pos < importPos && filePos.Column == 1 && !isCgoGenerated
   125  		if isAllowed {
   126  			autogenDebugf("file %q: pos=%d, filePos=%s: comment %q: it's allowed", filePath, pos, filePos, text)
   127  			neededComments = append(neededComments, text)
   128  		} else {
   129  			autogenDebugf("file %q: pos=%d, filePos=%s: comment %q: it's NOT allowed", filePath, pos, filePos, text)
   130  		}
   131  	}
   132  
   133  	autogenDebugf("file %q: got %d allowed comments", filePath, len(neededComments))
   134  
   135  	if len(neededComments) == 0 {
   136  		return ""
   137  	}
   138  
   139  	return strings.Join(neededComments, "\n")
   140  }
   141  
   142  func (p AutogeneratedExclude) Finish() {}