From 0404d6654828bebd20ab489124ed8d0a8c02838b Mon Sep 17 00:00:00 2001 From: SalvadorC Date: Sat, 28 Jul 2018 07:38:39 +0200 Subject: [PATCH] unnecessary-stmt (new rule) (#45) * simpler (new rule) * simpler: checks unitary switches * unnecessary-stmt (new rule) --- README.md | 1 + config.go | 1 + fixtures/unnecessary-stmt.go | 49 ++++++++++++++++ rule/unnecessary-stmt.go | 107 ++++++++++++++++++++++++++++++++++ test/unnecessary-stmt_test.go | 12 ++++ 5 files changed, 170 insertions(+) create mode 100644 fixtures/unnecessary-stmt.go create mode 100644 rule/unnecessary-stmt.go create mode 100644 test/unnecessary-stmt_test.go diff --git a/README.md b/README.md index 18246d1..6db1147 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | `unreachable-code` | n/a | Warns on unreachable code | no | no | | `add-constant` | map | Suggests using constant for magic numbers and string literals | no | no | | `flag-parameter` | n/a | Warns on boolean parameters that create a control coupling | no | no | +| `unnecessary-stmt` | n/a | Suggests removing or simplifying unnecessary statements | no | no | ## Available Formatters diff --git a/config.go b/config.go index 805b3b5..2379693 100644 --- a/config.go +++ b/config.go @@ -58,6 +58,7 @@ var allRules = append([]lint.Rule{ &rule.UnreachableCodeRule{}, &rule.AddConstantRule{}, &rule.FlagParamRule{}, + &rule.UnnecessaryStmtRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ diff --git a/fixtures/unnecessary-stmt.go b/fixtures/unnecessary-stmt.go new file mode 100644 index 0000000..9969a8a --- /dev/null +++ b/fixtures/unnecessary-stmt.go @@ -0,0 +1,49 @@ +package fixtures + +func foo(a, b, c, d int) { + switch n := node.(type) { // MATCH /switch with only one case can be replaced by an if-then/ + case *ast.SwitchStmt: + caseSelector := func(n ast.Node) bool { + _, ok := n.(*ast.CaseClause) + return ok + } + cases := pick(n.Body, caseSelector, nil) + if len(cases) == 1 { + cs, ok := cases[0].(*ast.CaseClause) + if ok && len(cs.List) == 1 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "style", + Failure: "switch can be replaced by an if-then", + }) + } + } + } +} + +func bar() { + a := 1 + + switch a { + case 1, 2: + a++ + } + +loop: + for { + switch a { + case 1: + a++ + println("one") + break // MATCH /omit unnecessary break at the end of case clause/ + case 2: + println("two") + break loop + default: + println("default") + } + } + + return // MATCH /omit unnecessary return statement/ +} diff --git a/rule/unnecessary-stmt.go b/rule/unnecessary-stmt.go new file mode 100644 index 0000000..50379bf --- /dev/null +++ b/rule/unnecessary-stmt.go @@ -0,0 +1,107 @@ +package rule + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// UnnecessaryStmtRule warns on unnecessary statements. +type UnnecessaryStmtRule struct{} + +// Apply applies the rule to given file. +func (r *UnnecessaryStmtRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintUnnecessaryStmtRule{onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *UnnecessaryStmtRule) Name() string { + return "unnecessary-stmt" +} + +type lintUnnecessaryStmtRule struct { + onFailure func(lint.Failure) +} + +func (w lintUnnecessaryStmtRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Body == nil || n.Type.Results != nil { + return w + } + stmts := n.Body.List + if len(stmts) == 0 { + return w + } + + lastStmt := stmts[len(stmts)-1] + rs, ok := lastStmt.(*ast.ReturnStmt) + if !ok { + return w + } + + if len(rs.Results) == 0 { + w.newFailure(lastStmt, "omit unnecessary return statement") + } + + case *ast.SwitchStmt: + w.checkSwitchBody(n.Body) + case *ast.TypeSwitchStmt: + w.checkSwitchBody(n.Body) + case *ast.CaseClause: + if n.Body == nil { + return w + } + stmts := n.Body + if len(stmts) == 0 { + return w + } + + lastStmt := stmts[len(stmts)-1] + rs, ok := lastStmt.(*ast.BranchStmt) + if !ok { + return w + } + + if rs.Tok == token.BREAK && rs.Label == nil { + w.newFailure(lastStmt, "omit unnecessary break at the end of case clause") + } + } + + return w +} + +func (w lintUnnecessaryStmtRule) checkSwitchBody(b *ast.BlockStmt) { + cases := b.List + if len(cases) != 1 { + return + } + + cc, ok := cases[0].(*ast.CaseClause) + if !ok { + return + } + + if len(cc.List) > 1 { // skip cases with multiple expressions + return + } + + w.newFailure(b, "switch with only one case can be replaced by an if-then") +} + +func (w lintUnnecessaryStmtRule) newFailure(node ast.Node, msg string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "style", + Failure: msg, + }) +} diff --git a/test/unnecessary-stmt_test.go b/test/unnecessary-stmt_test.go new file mode 100644 index 0000000..577a8aa --- /dev/null +++ b/test/unnecessary-stmt_test.go @@ -0,0 +1,12 @@ +package test + +import ( + "testing" + + "github.com/mgechev/revive/rule" +) + +// TestUnnecessaryStmt rule. +func TestUnnecessaryStmt(t *testing.T) { + testRule(t, "unnecessary-stmt", &rule.UnnecessaryStmtRule{}) +}