feat: allow the analysis of generated files (#4740)

This commit is contained in:
Ludovic Fernandez 2024-05-23 16:17:37 +02:00 committed by GitHub
parent 08deff4225
commit b99d5295f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 62 additions and 39 deletions

View File

@ -2872,17 +2872,17 @@ issues:
- ".*\\.my\\.go$" - ".*\\.my\\.go$"
- lib/bad.go - lib/bad.go
# To follow strictly the Go generated file convention. # Mode of the generated files analysis.
# #
# If set to true, source files that have lines matching only the following regular expression will be excluded: # - `strict`: sources are excluded by following strictly the Go generated file convention.
# `^// Code generated .* DO NOT EDIT\.$` # Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
# This line must appear before the first non-comment, non-blank text in the file. # This line must appear before the first non-comment, non-blank text in the file.
# https://go.dev/s/generatedcode # https://go.dev/s/generatedcode
# - `lax`: sources are excluded if they contain lines `autogenerated file`, `code generated`, `do not edit`, etc.
# - `disable`: disable the generated files exclusion.
# #
# By default, a lax pattern is applied: # Default: lax
# sources are excluded if they contain lines `autogenerated file`, `code generated`, `do not edit`, etc. exclude-generated: strict
# Default: false
exclude-generated-strict: true
# The list of ids of default excludes to include or disable. # The list of ids of default excludes to include or disable.
# https://golangci-lint.run/usage/false-positives/#default-exclusions # https://golangci-lint.run/usage/false-positives/#default-exclusions

View File

@ -3518,10 +3518,10 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"exclude-generated-strict": { "exclude-generated": {
"description": "To follow strict Go generated file convention", "description": "Mode of the generated files analysis.",
"type": "boolean", "enum": ["lax", "strict", "disable"],
"default": false "default": "lax"
}, },
"exclude-dirs": { "exclude-dirs": {
"description": "Which directories to exclude: issues from them won't be reported.", "description": "Which directories to exclude: issues from them won't be reported.",

View File

@ -103,6 +103,9 @@ func setupIssuesFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-dirs-use-default", "issues.exclude-dirs-use-default", true, internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-dirs-use-default", "issues.exclude-dirs-use-default", true,
getDefaultDirectoryExcludeHelp()) getDefaultDirectoryExcludeHelp())
internal.AddFlagAndBind(v, fs, fs.String, "exclude-generated", "issues.exclude-generated", processors.AutogeneratedModeLax,
color.GreenString("Mode of the generated files analysis"))
const newDesc = "Show only new issues: if there are unstaged changes or untracked files, only those changes " + const newDesc = "Show only new issues: if there are unstaged changes or untracked files, only those changes " +
"are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration " + "are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration " +
"of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at " + "of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at " +

View File

@ -108,12 +108,14 @@ type Issues struct {
ExcludeCaseSensitive bool `mapstructure:"exclude-case-sensitive"` ExcludeCaseSensitive bool `mapstructure:"exclude-case-sensitive"`
ExcludePatterns []string `mapstructure:"exclude"` ExcludePatterns []string `mapstructure:"exclude"`
ExcludeRules []ExcludeRule `mapstructure:"exclude-rules"` ExcludeRules []ExcludeRule `mapstructure:"exclude-rules"`
ExcludeGeneratedStrict bool `mapstructure:"exclude-generated-strict"`
UseDefaultExcludes bool `mapstructure:"exclude-use-default"` UseDefaultExcludes bool `mapstructure:"exclude-use-default"`
ExcludeFiles []string `mapstructure:"exclude-files"` ExcludeGenerated string `mapstructure:"exclude-generated"`
ExcludeDirs []string `mapstructure:"exclude-dirs"`
UseDefaultExcludeDirs bool `mapstructure:"exclude-dirs-use-default"` ExcludeFiles []string `mapstructure:"exclude-files"`
ExcludeDirs []string `mapstructure:"exclude-dirs"`
UseDefaultExcludeDirs bool `mapstructure:"exclude-dirs-use-default"`
MaxIssuesPerLinter int `mapstructure:"max-issues-per-linter"` MaxIssuesPerLinter int `mapstructure:"max-issues-per-linter"`
MaxSameIssues int `mapstructure:"max-same-issues"` MaxSameIssues int `mapstructure:"max-same-issues"`
@ -124,6 +126,8 @@ type Issues struct {
Diff bool `mapstructure:"new"` Diff bool `mapstructure:"new"`
NeedFix bool `mapstructure:"fix"` NeedFix bool `mapstructure:"fix"`
ExcludeGeneratedStrict bool `mapstructure:"exclude-generated-strict"` // Deprecated: use ExcludeGenerated instead.
} }
func (i *Issues) Validate() error { func (i *Issues) Validate() error {

View File

@ -357,6 +357,12 @@ func (l *Loader) handleDeprecation() error {
} }
} }
// Deprecated since v1.59.0
if l.cfg.Issues.ExcludeGeneratedStrict {
l.log.Warnf("The configuration option `issues.exclude-generated-strict` is deprecated, please use `issues.exclude-generated`")
l.cfg.Issues.ExcludeGenerated = "strict" // Don't use the constants to avoid cyclic dependencies.
}
l.handleLinterOptionDeprecations() l.handleLinterOptionDeprecations()
return nil return nil

View File

@ -75,7 +75,7 @@ func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *gouti
skipFilesProcessor, skipFilesProcessor,
skipDirsProcessor, // must be after path prettifier skipDirsProcessor, // must be after path prettifier
processors.NewAutogeneratedExclude(cfg.Issues.ExcludeGeneratedStrict), processors.NewAutogeneratedExclude(cfg.Issues.ExcludeGenerated),
// Must be before exclude because users see already marked output and configure excluding by it. // Must be before exclude because users see already marked output and configure excluding by it.
processors.NewIdentifierMarker(), processors.NewIdentifierMarker(),

View File

@ -12,6 +12,12 @@ import (
"github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result"
) )
const (
AutogeneratedModeLax = "lax"
AutogeneratedModeStrict = "strict"
AutogeneratedModeDisable = "disable"
)
const ( const (
genCodeGenerated = "code generated" genCodeGenerated = "code generated"
genDoNotEdit = "do not edit" genDoNotEdit = "do not edit"
@ -27,16 +33,16 @@ type fileSummary struct {
type AutogeneratedExclude struct { type AutogeneratedExclude struct {
debugf logutils.DebugFunc debugf logutils.DebugFunc
strict bool mode string
strictPattern *regexp.Regexp strictPattern *regexp.Regexp
fileSummaryCache map[string]*fileSummary fileSummaryCache map[string]*fileSummary
} }
func NewAutogeneratedExclude(strict bool) *AutogeneratedExclude { func NewAutogeneratedExclude(mode string) *AutogeneratedExclude {
return &AutogeneratedExclude{ return &AutogeneratedExclude{
debugf: logutils.Debug(logutils.DebugKeyAutogenExclude), debugf: logutils.Debug(logutils.DebugKeyAutogenExclude),
strict: strict, mode: mode,
strictPattern: regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`), strictPattern: regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`),
fileSummaryCache: map[string]*fileSummary{}, fileSummaryCache: map[string]*fileSummary{},
} }
@ -47,6 +53,10 @@ func (*AutogeneratedExclude) Name() string {
} }
func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, error) { func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, error) {
if p.mode == AutogeneratedModeDisable {
return issues, nil
}
return filterIssuesErr(issues, p.shouldPassIssue) return filterIssuesErr(issues, p.shouldPassIssue)
} }
@ -71,7 +81,7 @@ func (p *AutogeneratedExclude) shouldPassIssue(issue *result.Issue) (bool, error
fs = &fileSummary{} fs = &fileSummary{}
p.fileSummaryCache[issue.FilePath()] = fs p.fileSummaryCache[issue.FilePath()] = fs
if p.strict { if p.mode == AutogeneratedModeStrict {
var err error var err error
fs.generated, err = p.isGeneratedFileStrict(issue.FilePath()) fs.generated, err = p.isGeneratedFileStrict(issue.FilePath())
if err != nil { if err != nil {

View File

@ -14,7 +14,7 @@ import (
) )
func TestAutogeneratedExclude_isGeneratedFileLax_generated(t *testing.T) { func TestAutogeneratedExclude_isGeneratedFileLax_generated(t *testing.T) {
p := NewAutogeneratedExclude(false) p := NewAutogeneratedExclude(AutogeneratedModeLax)
comments := []string{ comments := []string{
` // generated by stringer -type Pill pill.go; DO NOT EDIT`, ` // generated by stringer -type Pill pill.go; DO NOT EDIT`,
@ -56,7 +56,7 @@ func TestAutogeneratedExclude_isGeneratedFileLax_generated(t *testing.T) {
} }
func TestAutogeneratedExclude_isGeneratedFileLax_nonGenerated(t *testing.T) { func TestAutogeneratedExclude_isGeneratedFileLax_nonGenerated(t *testing.T) {
p := NewAutogeneratedExclude(false) p := NewAutogeneratedExclude(AutogeneratedModeLax)
comments := []string{ comments := []string{
"code not generated by", "code not generated by",
@ -75,7 +75,7 @@ func TestAutogeneratedExclude_isGeneratedFileLax_nonGenerated(t *testing.T) {
} }
func TestAutogeneratedExclude_isGeneratedFileStrict(t *testing.T) { func TestAutogeneratedExclude_isGeneratedFileStrict(t *testing.T) {
p := NewAutogeneratedExclude(true) p := NewAutogeneratedExclude(AutogeneratedModeStrict)
testCases := []struct { testCases := []struct {
desc string desc string
@ -154,21 +154,21 @@ func Test_getComments_fileWithLongLine(t *testing.T) {
func Test_shouldPassIssue(t *testing.T) { func Test_shouldPassIssue(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
strict bool mode string
issue *result.Issue issue *result.Issue
assert assert.BoolAssertionFunc assert assert.BoolAssertionFunc
}{ }{
{ {
desc: "typecheck issue", desc: "typecheck issue",
strict: false, mode: AutogeneratedModeLax,
issue: &result.Issue{ issue: &result.Issue{
FromLinter: "typecheck", FromLinter: "typecheck",
}, },
assert: assert.True, assert: assert.True,
}, },
{ {
desc: "lax ", desc: "lax ",
strict: false, mode: AutogeneratedModeLax,
issue: &result.Issue{ issue: &result.Issue{
FromLinter: "example", FromLinter: "example",
Pos: token.Position{ Pos: token.Position{
@ -178,8 +178,8 @@ func Test_shouldPassIssue(t *testing.T) {
assert: assert.False, assert: assert.False,
}, },
{ {
desc: "strict ", desc: "strict ",
strict: true, mode: AutogeneratedModeStrict,
issue: &result.Issue{ issue: &result.Issue{
FromLinter: "example", FromLinter: "example",
Pos: token.Position{ Pos: token.Position{
@ -195,7 +195,7 @@ func Test_shouldPassIssue(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
p := NewAutogeneratedExclude(test.strict) p := NewAutogeneratedExclude(test.mode)
pass, err := p.shouldPassIssue(test.issue) pass, err := p.shouldPassIssue(test.issue)
require.NoError(t, err) require.NoError(t, err)
@ -213,13 +213,13 @@ func Test_shouldPassIssue_error(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
strict bool mode string
issue *result.Issue issue *result.Issue
expected string expected string
}{ }{
{ {
desc: "non-existing file (lax)", desc: "non-existing file (lax)",
strict: false, mode: AutogeneratedModeLax,
issue: &result.Issue{ issue: &result.Issue{
FromLinter: "example", FromLinter: "example",
Pos: token.Position{ Pos: token.Position{
@ -230,8 +230,8 @@ func Test_shouldPassIssue_error(t *testing.T) {
filepath.FromSlash("no-existing.go"), notFoundMsg), filepath.FromSlash("no-existing.go"), notFoundMsg),
}, },
{ {
desc: "non-existing file (strict)", desc: "non-existing file (strict)",
strict: true, mode: AutogeneratedModeStrict,
issue: &result.Issue{ issue: &result.Issue{
FromLinter: "example", FromLinter: "example",
Pos: token.Position{ Pos: token.Position{
@ -248,7 +248,7 @@ func Test_shouldPassIssue_error(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
p := NewAutogeneratedExclude(test.strict) p := NewAutogeneratedExclude(test.mode)
pass, err := p.shouldPassIssue(test.issue) pass, err := p.shouldPassIssue(test.issue)