From b22e68f34acfa2bce834ef3aadc3e71214255604 Mon Sep 17 00:00:00 2001 From: chavacava Date: Wed, 28 May 2025 15:30:00 +0200 Subject: [PATCH] wip: new rule enforce-switch-default --- config/config.go | 1 + rule/enforce_switch_default.go | 107 ++++++++++++++++++ test/enforce_switch_default_test.go | 18 +++ testdata/enforce_switch_default.go | 18 +++ ...enforce_switch_default_allow_no_default.go | 18 +++ .../enforce_switch_default_allow_not_last.go | 18 +++ 6 files changed, 180 insertions(+) create mode 100644 rule/enforce_switch_default.go create mode 100644 test/enforce_switch_default_test.go create mode 100644 testdata/enforce_switch_default.go create mode 100644 testdata/enforce_switch_default_allow_no_default.go create mode 100644 testdata/enforce_switch_default_allow_not_last.go diff --git a/config/config.go b/config/config.go index 869a152..9674f5f 100644 --- a/config/config.go +++ b/config/config.go @@ -104,6 +104,7 @@ var allRules = append([]lint.Rule{ &rule.RedundantTestMainExitRule{}, &rule.UnnecessaryFormatRule{}, &rule.UseFmtPrintRule{}, + &rule.EnforceSwitchDefaultRule{}, }, defaultRules...) // allFormatters is a list of all available formatters to output the linting results. diff --git a/rule/enforce_switch_default.go b/rule/enforce_switch_default.go new file mode 100644 index 0000000..cb90d77 --- /dev/null +++ b/rule/enforce_switch_default.go @@ -0,0 +1,107 @@ +package rule + +import ( + "fmt" + "go/ast" + "strings" + + "github.com/mgechev/revive/lint" +) + +// EnforceSwitchDefaultRule implements a rule to enforce default clauses use and/or position. +type EnforceSwitchDefaultRule 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 *EnforceSwitchDefaultRule) 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 strings.ToLower(argStr) { + case "allownodefault": + r.allowNoDefault = true + case "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 *EnforceSwitchDefaultRule) 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 (r *EnforceSwitchDefaultRule) 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 (*EnforceSwitchDefaultRule) Name() string { + return "enforce-switch-default" +} diff --git a/test/enforce_switch_default_test.go b/test/enforce_switch_default_test.go new file mode 100644 index 0000000..439e7e2 --- /dev/null +++ b/test/enforce_switch_default_test.go @@ -0,0 +1,18 @@ +package test + +import ( + "testing" + + "github.com/mgechev/revive/lint" + "github.com/mgechev/revive/rule" +) + +func TestEnforceSwitchDefault(t *testing.T) { + testRule(t, "enforce_switch_default", &rule.EnforceSwitchDefaultRule{}) + testRule(t, "enforce_switch_default_allow_no_default", &rule.EnforceSwitchDefaultRule{}, &lint.RuleConfig{ + Arguments: []any{"allowNoDefault"}, + }) + testRule(t, "enforce_switch_default_allow_not_last", &rule.EnforceSwitchDefaultRule{}, &lint.RuleConfig{ + Arguments: []any{"allowDefaultNotLast"}, + }) +} diff --git a/testdata/enforce_switch_default.go b/testdata/enforce_switch_default.go new file mode 100644 index 0000000..7ee8bfc --- /dev/null +++ b/testdata/enforce_switch_default.go @@ -0,0 +1,18 @@ +package fixtures + +func enforceSwitchDefault() { + + 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: + } +} diff --git a/testdata/enforce_switch_default_allow_no_default.go b/testdata/enforce_switch_default_allow_no_default.go new file mode 100644 index 0000000..d928b09 --- /dev/null +++ b/testdata/enforce_switch_default_allow_no_default.go @@ -0,0 +1,18 @@ +package fixtures + +func enforceSwitchDefault() { + + switch expression { + case condition: + default: + } + + switch expression { + default: // MATCH /default case clause must be the last one/ + case condition: + } + + switch expression { + case condition: + } +} diff --git a/testdata/enforce_switch_default_allow_not_last.go b/testdata/enforce_switch_default_allow_not_last.go new file mode 100644 index 0000000..2034e84 --- /dev/null +++ b/testdata/enforce_switch_default_allow_not_last.go @@ -0,0 +1,18 @@ +package fixtures + +func enforceSwitchDefault() { + + switch expression { + case condition: + default: + } + + switch expression { + default: + case condition: + } + + switch expression { // MATCH /switch must have a default case clause/ + case condition: + } +}