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  }