1package checkers
2
3import (
4	"go/ast"
5	"go/types"
6
7	"github.com/go-critic/go-critic/checkers/internal/astwalk"
8	"github.com/go-critic/go-critic/framework/linter"
9)
10
11func init() {
12	var info linter.CheckerInfo
13	info.Name = "caseOrder"
14	info.Tags = []string{"diagnostic"}
15	info.Summary = "Detects erroneous case order inside switch statements"
16	info.Before = `
17switch x.(type) {
18case ast.Expr:
19	fmt.Println("expr")
20case *ast.BasicLit:
21	fmt.Println("basic lit") // Never executed
22}`
23	info.After = `
24switch x.(type) {
25case *ast.BasicLit:
26	fmt.Println("basic lit") // Now reachable
27case ast.Expr:
28	fmt.Println("expr")
29}`
30
31	collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
32		return astwalk.WalkerForStmt(&caseOrderChecker{ctx: ctx}), nil
33	})
34}
35
36type caseOrderChecker struct {
37	astwalk.WalkHandler
38	ctx *linter.CheckerContext
39}
40
41func (c *caseOrderChecker) VisitStmt(stmt ast.Stmt) {
42	switch stmt := stmt.(type) {
43	case *ast.TypeSwitchStmt:
44		c.checkTypeSwitch(stmt)
45	case *ast.SwitchStmt:
46		c.checkSwitch(stmt)
47	}
48}
49
50func (c *caseOrderChecker) checkTypeSwitch(s *ast.TypeSwitchStmt) {
51	type ifaceType struct {
52		node ast.Node
53		typ  *types.Interface
54	}
55	var ifaces []ifaceType // Interfaces seen so far
56	for _, cc := range s.Body.List {
57		cc := cc.(*ast.CaseClause)
58		for _, x := range cc.List {
59			typ := c.ctx.TypeOf(x)
60			if typ == linter.UnknownType {
61				c.warnUnknownType(cc, x)
62				return
63			}
64			for _, iface := range ifaces {
65				if types.Implements(typ, iface.typ) {
66					c.warnTypeSwitch(cc, x, iface.node)
67					break
68				}
69			}
70			if iface, ok := typ.Underlying().(*types.Interface); ok {
71				ifaces = append(ifaces, ifaceType{node: x, typ: iface})
72			}
73		}
74	}
75}
76
77func (c *caseOrderChecker) warnTypeSwitch(cause, concrete, iface ast.Node) {
78	c.ctx.Warn(cause, "case %s must go before the %s case", concrete, iface)
79}
80
81func (c *caseOrderChecker) warnUnknownType(cause, concrete ast.Node) {
82	c.ctx.Warn(cause, "type is not defined %s", concrete)
83}
84
85func (c *caseOrderChecker) checkSwitch(s *ast.SwitchStmt) {
86	// TODO(quasilyte): can handle expression cases that overlap.
87	// Cases that have narrower value range should go before wider ones.
88}
89