github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/unexported-return.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 8 "github.com/mgechev/revive/lint" 9 ) 10 11 // UnexportedReturnRule lints given else constructs. 12 type UnexportedReturnRule struct{} 13 14 // Apply applies the rule to given file. 15 func (r *UnexportedReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { 16 var failures []lint.Failure 17 18 fileAst := file.AST 19 walker := lintUnexportedReturn{ 20 file: file, 21 fileAst: fileAst, 22 onFailure: func(failure lint.Failure) { 23 failures = append(failures, failure) 24 }, 25 } 26 27 file.Pkg.TypeCheck() 28 ast.Walk(walker, fileAst) 29 30 return failures 31 } 32 33 // Name returns the rule name. 34 func (r *UnexportedReturnRule) Name() string { 35 return "unexported-return" 36 } 37 38 type lintUnexportedReturn struct { 39 file *lint.File 40 fileAst *ast.File 41 onFailure func(lint.Failure) 42 } 43 44 func (w lintUnexportedReturn) Visit(n ast.Node) ast.Visitor { 45 fn, ok := n.(*ast.FuncDecl) 46 if !ok { 47 return w 48 } 49 if fn.Type.Results == nil { 50 return nil 51 } 52 if !fn.Name.IsExported() { 53 return nil 54 } 55 thing := "func" 56 if fn.Recv != nil && len(fn.Recv.List) > 0 { 57 thing = "method" 58 if !ast.IsExported(receiverType(fn)) { 59 // Don't report exported methods of unexported types, 60 // such as private implementations of sort.Interface. 61 return nil 62 } 63 } 64 for _, ret := range fn.Type.Results.List { 65 typ := w.file.Pkg.TypeOf(ret.Type) 66 if exportedType(typ) { 67 continue 68 } 69 w.onFailure(lint.Failure{ 70 Category: "unexported-type-in-api", 71 Node: ret.Type, 72 Confidence: 0.8, 73 Failure: fmt.Sprintf("exported %s %s returns unexported type %s, which can be annoying to use", 74 thing, fn.Name.Name, typ), 75 }) 76 break // only flag one 77 } 78 return nil 79 } 80 81 // exportedType reports whether typ is an exported type. 82 // It is imprecise, and will err on the side of returning true, 83 // such as for composite types. 84 func exportedType(typ types.Type) bool { 85 switch T := typ.(type) { 86 case *types.Named: 87 obj := T.Obj() 88 switch { 89 // Builtin types have no package. 90 case obj.Pkg() == nil: 91 case obj.Exported(): 92 default: 93 _, ok := T.Underlying().(*types.Interface) 94 return ok 95 } 96 return true 97 case *types.Map: 98 return exportedType(T.Key()) && exportedType(T.Elem()) 99 case interface { 100 Elem() types.Type 101 }: // array, slice, pointer, chan 102 return exportedType(T.Elem()) 103 } 104 // Be conservative about other types, such as struct, interface, etc. 105 return true 106 }