st1008.go raw
1 package st1008
2
3 import (
4 "go/types"
5
6 "honnef.co/go/tools/analysis/lint"
7 "honnef.co/go/tools/analysis/report"
8 "honnef.co/go/tools/internal/passes/buildir"
9
10 "golang.org/x/tools/go/analysis"
11 )
12
13 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
14 Analyzer: &analysis.Analyzer{
15 Name: "ST1008",
16 Run: run,
17 Requires: []*analysis.Analyzer{buildir.Analyzer},
18 },
19 Doc: &lint.RawDocumentation{
20 Title: `A function's error value should be its last return value`,
21 Text: `A function's error value should be its last return value.`,
22 Since: `2019.1`,
23 MergeIf: lint.MergeIfAny,
24 },
25 })
26
27 var Analyzer = SCAnalyzer.Analyzer
28
29 func run(pass *analysis.Pass) (interface{}, error) {
30 fnLoop:
31 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
32 sig := fn.Type().(*types.Signature)
33 rets := sig.Results()
34 if rets == nil || rets.Len() < 2 {
35 continue
36 }
37
38 if types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("error").Type() {
39 // Last return type is error. If the function also returns
40 // errors in other positions, that's fine.
41 continue
42 }
43
44 if rets.Len() >= 2 &&
45 types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("bool").Type() &&
46 types.Unalias(rets.At(rets.Len()-2).Type()) == types.Universe.Lookup("error").Type() {
47 // Accept (..., error, bool) and assume it's a comma-ok function. It's not clear whether the bool should come last or not for these kinds of functions.
48 continue
49 }
50 for i := rets.Len() - 2; i >= 0; i-- {
51 if types.Unalias(rets.At(i).Type()) == types.Universe.Lookup("error").Type() {
52 report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
53 continue fnLoop
54 }
55 }
56 }
57 return nil, nil
58 }
59