github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/config/issues.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  )
     7  
     8  const excludeRuleMinConditionsCount = 2
     9  
    10  var DefaultExcludePatterns = []ExcludePattern{
    11  	{
    12  		ID: "EXC0001",
    13  		Pattern: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close" +
    14  			"|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked",
    15  		Linter: "errcheck",
    16  		Why:    "Almost all programs ignore errors on these functions and in most cases it's ok",
    17  	},
    18  	{
    19  		ID: "EXC0002",
    20  		Pattern: "(comment on exported (method|function|type|const)|" +
    21  			"should have( a package)? comment|comment should be of the form)",
    22  		Linter: "golint",
    23  		Why:    "Annoying issue about not having a comment. The rare codebase has such comments",
    24  	},
    25  	{
    26  		ID:      "EXC0003",
    27  		Pattern: "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this",
    28  		Linter:  "golint",
    29  		Why:     "False positive when tests are defined in package 'test'",
    30  	},
    31  	{
    32  		ID:      "EXC0004",
    33  		Pattern: "(possible misuse of unsafe.Pointer|should have signature)",
    34  		Linter:  "govet",
    35  		Why:     "Common false positives",
    36  	},
    37  	{
    38  		ID:      "EXC0005",
    39  		Pattern: "ineffective break statement. Did you mean to break out of the outer loop",
    40  		Linter:  "staticcheck",
    41  		Why:     "Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore",
    42  	},
    43  	{
    44  		ID:      "EXC0006",
    45  		Pattern: "Use of unsafe calls should be audited",
    46  		Linter:  "gosec",
    47  		Why:     "Too many false-positives on 'unsafe' usage",
    48  	},
    49  	{
    50  		ID:      "EXC0007",
    51  		Pattern: "Subprocess launch(ed with variable|ing should be audited)",
    52  		Linter:  "gosec",
    53  		Why:     "Too many false-positives for parametrized shell calls",
    54  	},
    55  	{
    56  		ID:      "EXC0008",
    57  		Pattern: "(G104)",
    58  		Linter:  "gosec",
    59  		Why:     "Duplicated errcheck checks",
    60  	},
    61  	{
    62  		ID:      "EXC0009",
    63  		Pattern: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)",
    64  		Linter:  "gosec",
    65  		Why:     "Too many issues in popular repos",
    66  	},
    67  	{
    68  		ID:      "EXC0010",
    69  		Pattern: "Potential file inclusion via variable",
    70  		Linter:  "gosec",
    71  		Why:     "False positive is triggered by 'src, err := ioutil.ReadFile(filename)'",
    72  	},
    73  	{
    74  		ID: "EXC0011",
    75  		Pattern: "(comment on exported (method|function|type|const)|" +
    76  			"should have( a package)? comment|comment should be of the form)",
    77  		Linter: "stylecheck",
    78  		Why:    "Annoying issue about not having a comment. The rare codebase has such comments",
    79  	},
    80  	{
    81  		ID:      "EXC0012",
    82  		Pattern: `exported (.+) should have comment( \(or a comment on this block\))? or be unexported`,
    83  		Linter:  "revive",
    84  		Why:     "Annoying issue about not having a comment. The rare codebase has such comments",
    85  	},
    86  	{
    87  		ID:      "EXC0013",
    88  		Pattern: `package comment should be of the form "(.+)...`,
    89  		Linter:  "revive",
    90  		Why:     "Annoying issue about not having a comment. The rare codebase has such comments",
    91  	},
    92  	{
    93  		ID:      "EXC0014",
    94  		Pattern: `comment on exported (.+) should be of the form "(.+)..."`,
    95  		Linter:  "revive",
    96  		Why:     "Annoying issue about not having a comment. The rare codebase has such comments",
    97  	},
    98  	{
    99  		ID:      "EXC0015",
   100  		Pattern: `should have a package comment`,
   101  		Linter:  "revive",
   102  		Why:     "Annoying issue about not having a comment. The rare codebase has such comments",
   103  	},
   104  }
   105  
   106  type Issues struct {
   107  	IncludeDefaultExcludes []string      `mapstructure:"include"`
   108  	ExcludeCaseSensitive   bool          `mapstructure:"exclude-case-sensitive"`
   109  	ExcludePatterns        []string      `mapstructure:"exclude"`
   110  	ExcludeRules           []ExcludeRule `mapstructure:"exclude-rules"`
   111  	UseDefaultExcludes     bool          `mapstructure:"exclude-use-default"`
   112  
   113  	MaxIssuesPerLinter int `mapstructure:"max-issues-per-linter"`
   114  	MaxSameIssues      int `mapstructure:"max-same-issues"`
   115  
   116  	DiffFromRevision  string `mapstructure:"new-from-rev"`
   117  	DiffPatchFilePath string `mapstructure:"new-from-patch"`
   118  	WholeFiles        bool   `mapstructure:"whole-files"`
   119  	Diff              bool   `mapstructure:"new"`
   120  
   121  	NeedFix bool `mapstructure:"fix"`
   122  }
   123  
   124  type ExcludeRule struct {
   125  	BaseRule `mapstructure:",squash"`
   126  }
   127  
   128  func (e *ExcludeRule) Validate() error {
   129  	return e.BaseRule.Validate(excludeRuleMinConditionsCount)
   130  }
   131  
   132  type BaseRule struct {
   133  	Linters    []string
   134  	Path       string
   135  	PathExcept string `mapstructure:"path-except"`
   136  	Text       string
   137  	Source     string
   138  }
   139  
   140  func (b *BaseRule) Validate(minConditionsCount int) error {
   141  	if err := validateOptionalRegex(b.Path); err != nil {
   142  		return fmt.Errorf("invalid path regex: %w", err)
   143  	}
   144  	if err := validateOptionalRegex(b.PathExcept); err != nil {
   145  		return fmt.Errorf("invalid path-except regex: %w", err)
   146  	}
   147  	if err := validateOptionalRegex(b.Text); err != nil {
   148  		return fmt.Errorf("invalid text regex: %w", err)
   149  	}
   150  	if err := validateOptionalRegex(b.Source); err != nil {
   151  		return fmt.Errorf("invalid source regex: %w", err)
   152  	}
   153  	nonBlank := 0
   154  	if len(b.Linters) > 0 {
   155  		nonBlank++
   156  	}
   157  	// Filtering by path counts as one condition, regardless how it is done (one or both).
   158  	// Otherwise, a rule with Path and PathExcept set would pass validation
   159  	// whereas before the introduction of path-except that wouldn't have been precise enough.
   160  	if b.Path != "" || b.PathExcept != "" {
   161  		nonBlank++
   162  	}
   163  	if b.Text != "" {
   164  		nonBlank++
   165  	}
   166  	if b.Source != "" {
   167  		nonBlank++
   168  	}
   169  	if nonBlank < minConditionsCount {
   170  		return fmt.Errorf("at least %d of (text, source, path[-except],  linters) should be set", minConditionsCount)
   171  	}
   172  	return nil
   173  }
   174  
   175  func validateOptionalRegex(value string) error {
   176  	if value == "" {
   177  		return nil
   178  	}
   179  	_, err := regexp.Compile(value)
   180  	return err
   181  }
   182  
   183  type ExcludePattern struct {
   184  	ID      string
   185  	Pattern string
   186  	Linter  string
   187  	Why     string
   188  }
   189  
   190  func GetDefaultExcludePatternsStrings() []string {
   191  	ret := make([]string, len(DefaultExcludePatterns))
   192  	for i, p := range DefaultExcludePatterns {
   193  		ret[i] = p.Pattern
   194  	}
   195  	return ret
   196  }
   197  
   198  // TODO(ldez): this behavior must be changed in v2, because this is confusing.
   199  func GetExcludePatterns(include []string) []ExcludePattern {
   200  	includeMap := make(map[string]struct{}, len(include))
   201  	for _, inc := range include {
   202  		includeMap[inc] = struct{}{}
   203  	}
   204  
   205  	var ret []ExcludePattern
   206  	for _, p := range DefaultExcludePatterns {
   207  		if _, ok := includeMap[p.ID]; !ok {
   208  			ret = append(ret, p)
   209  		}
   210  	}
   211  
   212  	return ret
   213  }