1package checkers
2
3import (
4	"go/ast"
5
6	"github.com/go-critic/go-critic/checkers/internal/astwalk"
7	"github.com/go-critic/go-critic/framework/linter"
8)
9
10func init() {
11	var info linter.CheckerInfo
12	info.Name = "nestingReduce"
13	info.Tags = []string{"style", "opinionated", "experimental"}
14	info.Params = linter.CheckerParams{
15		"bodyWidth": {
16			Value: 5,
17			Usage: "min number of statements inside a branch to trigger a warning",
18		},
19	}
20	info.Summary = "Finds where nesting level could be reduced"
21	info.Before = `
22for _, v := range a {
23	if v.Bool {
24		body()
25	}
26}`
27	info.After = `
28for _, v := range a {
29	if !v.Bool {
30		continue
31	}
32	body()
33}`
34
35	collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
36		c := &nestingReduceChecker{ctx: ctx}
37		c.bodyWidth = info.Params.Int("bodyWidth")
38		return astwalk.WalkerForStmt(c), nil
39	})
40}
41
42type nestingReduceChecker struct {
43	astwalk.WalkHandler
44	ctx *linter.CheckerContext
45
46	bodyWidth int
47}
48
49func (c *nestingReduceChecker) VisitStmt(stmt ast.Stmt) {
50	switch stmt := stmt.(type) {
51	case *ast.ForStmt:
52		c.checkLoopBody(stmt.Body.List)
53	case *ast.RangeStmt:
54		c.checkLoopBody(stmt.Body.List)
55	}
56}
57
58func (c *nestingReduceChecker) checkLoopBody(body []ast.Stmt) {
59	if len(body) != 1 {
60		return
61	}
62	stmt, ok := body[0].(*ast.IfStmt)
63	if !ok {
64		return
65	}
66	if len(stmt.Body.List) >= c.bodyWidth && stmt.Else == nil {
67		c.warnLoop(stmt)
68	}
69}
70
71func (c *nestingReduceChecker) warnLoop(cause ast.Node) {
72	c.ctx.Warn(cause, "invert if cond, replace body with `continue`, move old body after the statement")
73}
74