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() {}