This repository has been archived by the owner on Dec 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
unnamedResult_checker.go
103 lines (89 loc) · 2.56 KB
/
unnamedResult_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package checkers
import (
"go/ast"
"go/types"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "unnamedResult"
info.Tags = []string{"style", "opinionated", "experimental"}
info.Params = lintpack.CheckerParams{
"checkExported": {
Value: false,
Usage: "whether to check exported functions",
},
}
info.Summary = "Detects unnamed results that may benefit from names"
info.Before = `func f() (float64, float64)`
info.After = `func f() (x, y float64)`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
c := &unnamedResultChecker{ctx: ctx}
c.checkExported = info.Params.Bool("checkExported")
return astwalk.WalkerForFuncDecl(c)
})
}
type unnamedResultChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
checkExported bool
}
func (c *unnamedResultChecker) VisitFuncDecl(decl *ast.FuncDecl) {
if c.checkExported && !ast.IsExported(decl.Name.Name) {
return
}
results := decl.Type.Results
switch {
case results == nil:
return // Function has no results
case len(results.List) > 0 && results.List[0].Names != nil:
return // Skip named results
}
typeName := func(x ast.Expr) string { return c.typeName(c.ctx.TypesInfo.TypeOf(x)) }
isError := func(x ast.Expr) bool { return qualifiedName(x) == "error" }
isBool := func(x ast.Expr) bool { return qualifiedName(x) == "bool" }
// Main difference with case of len=2 is that we permit any
// typ1 as long as second type is either error or bool.
if results.NumFields() == 2 {
typ1, typ2 := results.List[0].Type, results.List[1].Type
name1, name2 := typeName(typ1), typeName(typ2)
cond := (name1 != name2 && name2 != "") ||
(!isError(typ1) && isError(typ2)) ||
(!isBool(typ1) && isBool(typ2))
if !cond {
c.warn(decl)
}
return
}
seen := make(map[string]bool, len(results.List))
for i := range results.List {
typ := results.List[i].Type
name := typeName(typ)
isLast := i == len(results.List)-1
cond := !seen[name] ||
(isLast && (isError(typ) || isBool(typ)))
if !cond {
c.warn(decl)
return
}
seen[name] = true
}
}
func (c *unnamedResultChecker) typeName(typ types.Type) string {
switch typ := typ.(type) {
case *types.Array:
return c.typeName(typ.Elem())
case *types.Pointer:
return c.typeName(typ.Elem())
case *types.Slice:
return c.typeName(typ.Elem())
case *types.Named:
return typ.Obj().Name()
default:
return ""
}
}
func (c *unnamedResultChecker) warn(n ast.Node) {
c.ctx.Warn(n, "consider giving a name to these results")
}