github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/result/processors/autogenerated_exclude.go (about)

     1  package processors
     2  
     3  import (
     4  	"fmt"
     5  	"go/parser"
     6  	"go/token"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/elek/golangci-lint/pkg/logutils"
    13  	"github.com/elek/golangci-lint/pkg/result"
    14  )
    15  
    16  var autogenDebugf = logutils.Debug("autogen_exclude")
    17  
    18  type ageFileSummary struct {
    19  	isGenerated bool
    20  }
    21  
    22  type ageFileSummaryCache map[string]*ageFileSummary
    23  
    24  type AutogeneratedExclude struct {
    25  	fileSummaryCache ageFileSummaryCache
    26  }
    27  
    28  func NewAutogeneratedExclude() *AutogeneratedExclude {
    29  	return &AutogeneratedExclude{
    30  		fileSummaryCache: ageFileSummaryCache{},
    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 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  
    50  func (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.
    76  func 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  
    96  func (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  
   119  func 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  
   134  func (p AutogeneratedExclude) Finish() {}