1package checkers
2
3import (
4	"go/ast"
5	"go/token"
6
7	"github.com/go-critic/go-critic/checkers/internal/astwalk"
8	"github.com/go-critic/go-critic/framework/linter"
9	"github.com/go-toolsmith/astequal"
10	"github.com/go-toolsmith/typep"
11)
12
13func init() {
14	var info linter.CheckerInfo
15	info.Name = "nilValReturn"
16	info.Tags = []string{"diagnostic", "experimental"}
17	info.Summary = "Detects return statements those results evaluate to nil"
18	info.Before = `
19if err == nil {
20	return err
21}`
22	info.After = `
23// (A) - return nil explicitly
24if err == nil {
25	return nil
26}
27// (B) - typo in "==", change to "!="
28if err != nil {
29	return err
30}`
31
32	collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
33		return astwalk.WalkerForStmt(&nilValReturnChecker{ctx: ctx}), nil
34	})
35}
36
37type nilValReturnChecker struct {
38	astwalk.WalkHandler
39	ctx *linter.CheckerContext
40}
41
42func (c *nilValReturnChecker) VisitStmt(stmt ast.Stmt) {
43	ifStmt, ok := stmt.(*ast.IfStmt)
44	if !ok || len(ifStmt.Body.List) != 1 {
45		return
46	}
47	ret, ok := ifStmt.Body.List[0].(*ast.ReturnStmt)
48	if !ok {
49		return
50	}
51	expr, ok := ifStmt.Cond.(*ast.BinaryExpr)
52	if !ok {
53		return
54	}
55	xIsNil := expr.Op == token.EQL &&
56		typep.SideEffectFree(c.ctx.TypesInfo, expr.X) &&
57		qualifiedName(expr.Y) == "nil"
58	if !xIsNil {
59		return
60	}
61	for _, res := range ret.Results {
62		if astequal.Expr(expr.X, res) {
63			c.warn(ret, expr.X)
64			break
65		}
66	}
67}
68
69func (c *nilValReturnChecker) warn(cause, val ast.Node) {
70	c.ctx.Warn(cause, "returned expr is always nil; replace %s with nil", val)
71}
72