mirror of
https://github.com/mgechev/revive.git
synced 2025-07-17 01:12:27 +02:00
feature: new rule enforce-switch-default (#1390)
This commit is contained in:
@ -495,6 +495,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
|
||||
| [`enforce-map-style`](./RULES_DESCRIPTIONS.md#enforce-map-style) | string (defaults to "any") | Enforces consistent usage of `make(map[type]type)` or `map[type]type{}` for map initialization. Does not affect `make(map[type]type, size)` constructions. | no | no |
|
||||
| [`enforce-repeated-arg-type-style`](./RULES_DESCRIPTIONS.md#enforce-repeated-arg-type-style) | string (defaults to "any") | Enforces consistent style for repeated argument and/or return value types. | no | no |
|
||||
| [`enforce-slice-style`](./RULES_DESCRIPTIONS.md#enforce-slice-style) | string (defaults to "any") | Enforces consistent usage of `make([]type, 0)` or `[]type{}` for slice initialization. Does not affect `make(map[type]type, non_zero_len, or_non_zero_cap)` constructions. | no | no |
|
||||
| [`enforce-switch-style`](./RULES_DESCRIPTIONS.md#enforce-switch-style) | []string (defaults to enforce occurrence and position) | Enforces consistent usage of `default` on `switch` statements. | no | no |
|
||||
| [`error-naming`](./RULES_DESCRIPTIONS.md#error-naming) | n/a | Naming of error variables. | yes | no |
|
||||
| [`error-return`](./RULES_DESCRIPTIONS.md#error-return) | n/a | The error return parameter should be last. | yes | no |
|
||||
| [`error-strings`](./RULES_DESCRIPTIONS.md#error-strings) | []string | Conventions around error strings. | yes | no |
|
||||
|
@ -31,6 +31,7 @@ List of all available rules.
|
||||
- [enforce-map-style](#enforce-map-style)
|
||||
- [enforce-repeated-arg-type-style](#enforce-repeated-arg-type-style)
|
||||
- [enforce-slice-style](#enforce-slice-style)
|
||||
- [enforce-switch-style](#enforce-switch-style)
|
||||
- [error-naming](#error-naming)
|
||||
- [error-return](#error-return)
|
||||
- [error-strings](#error-strings)
|
||||
@ -520,6 +521,40 @@ Example:
|
||||
arguments = ["make"]
|
||||
```
|
||||
|
||||
## enforce-switch-style
|
||||
|
||||
_Description_: This rule enforces consistent usage of `default` on `switch` statements.
|
||||
It can check for `default` case clause occurrence and/or position in the list of case clauses.
|
||||
|
||||
_Configuration_: ([]string) Specifies what to enforced: occurrence and/or position. The, non-mutually exclusive, options are:
|
||||
|
||||
- "allowNoDefault": allows `switch` without `default` case clause.
|
||||
- "allowDefaultNotLast": allows `default` case clause to be not the last clause of the `switch`.
|
||||
|
||||
Examples:
|
||||
|
||||
To enforce that all `switch` statements have a `default` clause as its the last case clause:
|
||||
|
||||
```toml
|
||||
[rule.enforce-switch-style]
|
||||
```
|
||||
|
||||
To enforce that all `switch` statements have a `default` clause but its position is unimportant:
|
||||
|
||||
```toml
|
||||
[rule.enforce-switch-style]
|
||||
arguments = ["allowDefaultNotLast"]
|
||||
```
|
||||
|
||||
To enforce that in all `switch` statements with a `default` clause, the `default` is the last case clause:
|
||||
|
||||
```toml
|
||||
[rule.enforce-switch-style]
|
||||
arguments = ["allowNoDefault"]
|
||||
```
|
||||
|
||||
Notice that a configuration including both options will effectively deactivate the whole rule.
|
||||
|
||||
## error-naming
|
||||
|
||||
_Description_: By convention, for the sake of readability, variables of type `error` must be named with the prefix `err`.
|
||||
|
@ -104,6 +104,7 @@ var allRules = append([]lint.Rule{
|
||||
&rule.RedundantTestMainExitRule{},
|
||||
&rule.UnnecessaryFormatRule{},
|
||||
&rule.UseFmtPrintRule{},
|
||||
&rule.EnforceSwitchStyleRule{},
|
||||
}, defaultRules...)
|
||||
|
||||
// allFormatters is a list of all available formatters to output the linting results.
|
||||
|
106
rule/enforce_switch_style.go
Normal file
106
rule/enforce_switch_style.go
Normal file
@ -0,0 +1,106 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
// EnforceSwitchStyleRule implements a rule to enforce default clauses use and/or position.
|
||||
type EnforceSwitchStyleRule struct {
|
||||
allowNoDefault bool // allow absence of default
|
||||
allowDefaultNotLast bool // allow default, if present, not being the last case
|
||||
}
|
||||
|
||||
// Configure validates the rule configuration, and configures the rule accordingly.
|
||||
//
|
||||
// Configuration implements the [lint.ConfigurableRule] interface.
|
||||
func (r *EnforceSwitchStyleRule) Configure(arguments lint.Arguments) error {
|
||||
if len(arguments) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, arg := range arguments {
|
||||
argStr, ok := arg.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid argument for rule %s; expected string but got %T", r.Name(), arg)
|
||||
}
|
||||
switch {
|
||||
case isRuleOption(argStr, "allowNoDefault"):
|
||||
r.allowNoDefault = true
|
||||
case isRuleOption(argStr, "allowDefaultNotLast"):
|
||||
r.allowDefaultNotLast = true
|
||||
default:
|
||||
return fmt.Errorf(`invalid argument %q for rule %s; expected "allowNoDefault" or "allowDefaultNotLast"`, argStr, r.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply applies the rule to given file.
|
||||
func (r *EnforceSwitchStyleRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
||||
var failures []lint.Failure
|
||||
astFile := file.AST
|
||||
ast.Inspect(astFile, func(n ast.Node) bool {
|
||||
switchNode, ok := n.(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return true // not a switch statement
|
||||
}
|
||||
|
||||
defaultClause, isLast := r.seekDefaultCase(switchNode.Body)
|
||||
hasDefault := defaultClause != nil
|
||||
|
||||
if !hasDefault && r.allowNoDefault {
|
||||
return true // switch without default but the rule is configured to don´t care
|
||||
}
|
||||
|
||||
if !hasDefault && !r.allowNoDefault {
|
||||
// switch without default
|
||||
failures = append(failures, lint.Failure{
|
||||
Confidence: 1,
|
||||
Node: switchNode,
|
||||
Category: lint.FailureCategoryStyle,
|
||||
Failure: "switch must have a default case clause",
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// the switch has a default
|
||||
|
||||
if r.allowDefaultNotLast || isLast {
|
||||
return true
|
||||
}
|
||||
|
||||
failures = append(failures, lint.Failure{
|
||||
Confidence: 1,
|
||||
Node: defaultClause,
|
||||
Category: lint.FailureCategoryStyle,
|
||||
Failure: "default case clause must be the last one",
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
func (*EnforceSwitchStyleRule) seekDefaultCase(body *ast.BlockStmt) (defaultClause *ast.CaseClause, isLast bool) {
|
||||
var last *ast.CaseClause
|
||||
for _, stmt := range body.List {
|
||||
cc, _ := stmt.(*ast.CaseClause) // no need to check for ok
|
||||
last = cc
|
||||
if cc.List == nil { // a nil List means "default"
|
||||
defaultClause = cc
|
||||
}
|
||||
}
|
||||
|
||||
return defaultClause, defaultClause == last
|
||||
}
|
||||
|
||||
// Name returns the rule name.
|
||||
func (*EnforceSwitchStyleRule) Name() string {
|
||||
return "enforce-switch-style"
|
||||
}
|
92
rule/enforce_switch_style_test.go
Normal file
92
rule/enforce_switch_style_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
func TestEnforceSwitchStyleRule_Configure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arguments lint.Arguments
|
||||
wantErr error
|
||||
wantAllowNoDefault bool
|
||||
wantAllowDefaultNotLast bool
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
arguments: lint.Arguments{},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "valid argument: allowNoDefault",
|
||||
arguments: lint.Arguments{
|
||||
"allowNoDefault",
|
||||
},
|
||||
wantErr: nil,
|
||||
wantAllowNoDefault: true,
|
||||
},
|
||||
{
|
||||
name: "valid argument: allowDefaultNotLast",
|
||||
arguments: lint.Arguments{
|
||||
"allowDefaultNotLast",
|
||||
},
|
||||
wantErr: nil,
|
||||
wantAllowDefaultNotLast: true,
|
||||
},
|
||||
{
|
||||
name: "valid lowercased arguments",
|
||||
arguments: lint.Arguments{
|
||||
"allowdefaultnotlast",
|
||||
"allownodefault",
|
||||
},
|
||||
wantErr: nil,
|
||||
wantAllowNoDefault: true,
|
||||
wantAllowDefaultNotLast: true,
|
||||
},
|
||||
{
|
||||
name: "unknown argument: unknown",
|
||||
arguments: lint.Arguments{
|
||||
"unknown",
|
||||
},
|
||||
wantErr: errors.New(`invalid argument "unknown" for rule enforce-switch-style; expected "allowNoDefault" or "allowDefaultNotLast"`),
|
||||
},
|
||||
{
|
||||
name: "unexpected type argument: 10",
|
||||
arguments: lint.Arguments{
|
||||
10,
|
||||
},
|
||||
wantErr: errors.New(`invalid argument for rule enforce-switch-style; expected string but got int`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var rule EnforceSwitchStyleRule
|
||||
|
||||
err := rule.Configure(tt.arguments)
|
||||
|
||||
if tt.wantErr != nil {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
|
||||
return
|
||||
}
|
||||
if err.Error() != tt.wantErr.Error() {
|
||||
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: got = %v, want = nil", err)
|
||||
}
|
||||
if tt.wantAllowNoDefault != rule.allowNoDefault {
|
||||
t.Errorf("unexpected allowNoDefault: want = %v, got %v", tt.wantAllowNoDefault, rule.allowNoDefault)
|
||||
}
|
||||
if tt.wantAllowDefaultNotLast != rule.allowDefaultNotLast {
|
||||
t.Errorf("unexpected funcRetValStyle: want = %v, got %v", tt.wantAllowDefaultNotLast, rule.allowDefaultNotLast)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
21
test/enforce_switch_style_test.go
Normal file
21
test/enforce_switch_style_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mgechev/revive/lint"
|
||||
"github.com/mgechev/revive/rule"
|
||||
)
|
||||
|
||||
func TestEnforceSwitchStyle(t *testing.T) {
|
||||
testRule(t, "enforce_switch_style", &rule.EnforceSwitchStyleRule{})
|
||||
testRule(t, "enforce_switch_style_allow_no_default", &rule.EnforceSwitchStyleRule{}, &lint.RuleConfig{
|
||||
Arguments: []any{"allowNoDefault"},
|
||||
})
|
||||
testRule(t, "enforce_switch_style_allow_not_last", &rule.EnforceSwitchStyleRule{}, &lint.RuleConfig{
|
||||
Arguments: []any{"allowDefaultNotLast"},
|
||||
})
|
||||
testRule(t, "enforce_switch_style_allow_no_default_allow_not_last", &rule.EnforceSwitchStyleRule{}, &lint.RuleConfig{
|
||||
Arguments: []any{"allowNoDefault", "allowDefaultNotLast"},
|
||||
})
|
||||
}
|
18
testdata/enforce_switch_style.go
vendored
Normal file
18
testdata/enforce_switch_style.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package fixtures
|
||||
|
||||
func enforceSwitchStyle3() {
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
default:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
default: // MATCH /default case clause must be the last one/
|
||||
case condition:
|
||||
}
|
||||
|
||||
switch expression { // MATCH /switch must have a default case clause/
|
||||
case condition:
|
||||
}
|
||||
}
|
18
testdata/enforce_switch_style_allow_no_default.go
vendored
Normal file
18
testdata/enforce_switch_style_allow_no_default.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package fixtures
|
||||
|
||||
func enforceSwitchStyle() {
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
default:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
default: // MATCH /default case clause must be the last one/
|
||||
case condition:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
}
|
||||
}
|
18
testdata/enforce_switch_style_allow_no_default_allow_not_last.go
vendored
Normal file
18
testdata/enforce_switch_style_allow_no_default_allow_not_last.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package fixtures
|
||||
|
||||
func enforceSwitchStyle3() {
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
default:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
default:
|
||||
case condition:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
}
|
||||
}
|
18
testdata/enforce_switch_style_allow_not_last.go
vendored
Normal file
18
testdata/enforce_switch_style_allow_not_last.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package fixtures
|
||||
|
||||
func enforceSwitchStyle2() {
|
||||
|
||||
switch expression {
|
||||
case condition:
|
||||
default:
|
||||
}
|
||||
|
||||
switch expression {
|
||||
default:
|
||||
case condition:
|
||||
}
|
||||
|
||||
switch expression { // MATCH /switch must have a default case clause/
|
||||
case condition:
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user