Check for redundant if err != nil constructs. (#319)
* Check for redundant if err != nil constructs.
Detect and complain about constructs like:
if err := foo(); err != nil {
return err
}
return nil
(Issue https://github.com/golang/lint/issues/312)
* Rewrite lintIfError without using matchers.
Also don't emit lint errors if there are any comments explaining the
construct (between if and return statements).
diff --git a/lint.go b/lint.go
index fb47da0..935ac83 100644
--- a/lint.go
+++ b/lint.go
@@ -199,6 +199,7 @@
f.lintNames()
f.lintVarDecls()
f.lintElses()
+ f.lintIfError()
f.lintRanges()
f.lintErrorf()
f.lintErrors()
@@ -1466,6 +1467,85 @@
})
}
+// containsComments returns whether the interval [start, end) contains any
+// comments without "// MATCH " prefix.
+func (f *file) containsComments(start, end token.Pos) bool {
+ for _, cgroup := range f.f.Comments {
+ comments := cgroup.List
+ if comments[0].Slash >= end {
+ // All comments starting with this group are after end pos.
+ return false
+ }
+ if comments[len(comments)-1].Slash < start {
+ // Comments group ends before start pos.
+ continue
+ }
+ for _, c := range comments {
+ if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (f *file) lintIfError() {
+ f.walk(func(node ast.Node) bool {
+ switch v := node.(type) {
+ case *ast.BlockStmt:
+ for i := 0; i < len(v.List)-1; i++ {
+ // if var := whatever; var != nil { return var }
+ s, ok := v.List[i].(*ast.IfStmt)
+ if !ok || s.Body == nil || len(s.Body.List) != 1 || s.Else != nil {
+ continue
+ }
+ assign, ok := s.Init.(*ast.AssignStmt)
+ if !ok || len(assign.Lhs) != 1 || !(assign.Tok == token.DEFINE || assign.Tok == token.ASSIGN) {
+ continue
+ }
+ id, ok := assign.Lhs[0].(*ast.Ident)
+ if !ok {
+ continue
+ }
+ expr, ok := s.Cond.(*ast.BinaryExpr)
+ if !ok || expr.Op != token.NEQ {
+ continue
+ }
+ if lhs, ok := expr.X.(*ast.Ident); !ok || lhs.Name != id.Name {
+ continue
+ }
+ if rhs, ok := expr.Y.(*ast.Ident); !ok || rhs.Name != "nil" {
+ continue
+ }
+ r, ok := s.Body.List[0].(*ast.ReturnStmt)
+ if !ok || len(r.Results) != 1 {
+ continue
+ }
+ if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != id.Name {
+ continue
+ }
+
+ // return nil
+ r, ok = v.List[i+1].(*ast.ReturnStmt)
+ if !ok || len(r.Results) != 1 {
+ continue
+ }
+ if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != "nil" {
+ continue
+ }
+
+ // check if there are any comments explaining the construct, don't emit an error if there are some.
+ if f.containsComments(s.Pos(), r.Pos()) {
+ continue
+ }
+
+ f.errorf(v.List[i], 0.9, "redundant if ...; err != nil check, just return error instead.")
+ }
+ }
+ return true
+ })
+}
+
// receiverType returns the named type of the method receiver, sans "*",
// or "invalid-type" if fn.Recv is ill formed.
func receiverType(fn *ast.FuncDecl) string {
diff --git a/testdata/iferr.go b/testdata/iferr.go
new file mode 100644
index 0000000..920fa9e
--- /dev/null
+++ b/testdata/iferr.go
@@ -0,0 +1,101 @@
+// Test of redundant if err != nil
+
+// Package pkg ...
+package pkg
+
+func f() error {
+ if err := f(); err != nil {
+ g()
+ return err
+ }
+ return nil
+}
+
+func g() error {
+ if err := f(); err != nil { // MATCH /redundant/
+ return err
+ }
+ return nil
+}
+
+func h() error {
+ if err, x := f(), 1; err != nil {
+ return err
+ }
+ return nil
+}
+
+func i() error {
+ a := 1
+ if err := f(); err != nil {
+ a++
+ return err
+ }
+ return nil
+}
+
+func j() error {
+ var a error
+ if err := f(); err != nil {
+ return err
+ }
+ return a
+}
+
+func k() error {
+ if err := f(); err != nil {
+ // TODO: handle error better
+ return err
+ }
+ return nil
+}
+
+func l() (interface{}, error) {
+ if err := f(); err != nil {
+ return nil, err
+ }
+ if err := f(); err != nil {
+ return nil, err
+ }
+ if err := f(); err != nil {
+ return nil, err
+ }
+ // Phew, it worked
+ return nil
+}
+
+func m() error {
+ if err := f(); err != nil {
+ return err
+ }
+ if err := f(); err != nil {
+ return err
+ }
+ if err := f(); err != nil {
+ return err
+ }
+ // Phew, it worked again.
+ return nil
+}
+
+func multi() error {
+ a := 0
+ var err error
+ // unreachable code after return statements is intentional to check that it
+ // doesn't confuse the linter.
+ if true {
+ a++
+ if err := f(); err != nil { // MATCH /redundant/
+ return err
+ }
+ return nil
+ a++
+ } else {
+ a++
+ if err = f(); err != nil { // MATCH /redundant/
+ return err
+ }
+ return nil
+ a++
+ }
+}