...
1
2
3
4
5
6
7 package deepequalerrors
8
9 import (
10 "go/ast"
11 "go/types"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
16 "golang.org/x/tools/go/ast/inspector"
17 "golang.org/x/tools/go/types/typeutil"
18 "golang.org/x/tools/internal/aliases"
19 )
20
21 const Doc = `check for calls of reflect.DeepEqual on error values
22
23 The deepequalerrors checker looks for calls of the form:
24
25 reflect.DeepEqual(err1, err2)
26
27 where err1 and err2 are errors. Using reflect.DeepEqual to compare
28 errors is discouraged.`
29
30 var Analyzer = &analysis.Analyzer{
31 Name: "deepequalerrors",
32 Doc: Doc,
33 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors",
34 Requires: []*analysis.Analyzer{inspect.Analyzer},
35 Run: run,
36 }
37
38 func run(pass *analysis.Pass) (interface{}, error) {
39 if !analysisutil.Imports(pass.Pkg, "reflect") {
40 return nil, nil
41 }
42
43 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
44
45 nodeFilter := []ast.Node{
46 (*ast.CallExpr)(nil),
47 }
48 inspect.Preorder(nodeFilter, func(n ast.Node) {
49 call := n.(*ast.CallExpr)
50 fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
51 if analysisutil.IsFunctionNamed(fn, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
52 pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors")
53 }
54 })
55 return nil, nil
56 }
57
58 var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
59
60
61
62 func hasError(pass *analysis.Pass, e ast.Expr) bool {
63 tv, ok := pass.TypesInfo.Types[e]
64 if !ok {
65 return false
66 }
67 return containsError(tv.Type)
68 }
69
70
71
72
73
74
75 func containsError(typ types.Type) bool {
76
77
78
79 inProgress := make(map[types.Type]bool)
80
81 var check func(t types.Type) bool
82 check = func(t types.Type) bool {
83 if t == errorType {
84 return true
85 }
86 if inProgress[t] {
87 return false
88 }
89 inProgress[t] = true
90 switch t := t.(type) {
91 case *types.Pointer:
92 return check(t.Elem())
93 case *types.Slice:
94 return check(t.Elem())
95 case *types.Array:
96 return check(t.Elem())
97 case *types.Map:
98 return check(t.Key()) || check(t.Elem())
99 case *types.Struct:
100 for i := 0; i < t.NumFields(); i++ {
101 if check(t.Field(i).Type()) {
102 return true
103 }
104 }
105 case *types.Named, *aliases.Alias:
106 return check(t.Underlying())
107
108
109 case *types.Basic:
110 case *types.Chan:
111 case *types.Signature:
112 case *types.Tuple:
113 case *types.Interface:
114 }
115 return false
116 }
117
118 return check(typ)
119 }
120
View as plain text