github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/unhandled-error.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  
     8  	"github.com/songshiyun/revive/lint"
     9  )
    10  
    11  // UnhandledErrorRule lints given else constructs.
    12  type UnhandledErrorRule struct {
    13  	ignoreList ignoreListType
    14  }
    15  
    16  type ignoreListType map[string]struct{}
    17  
    18  // Apply applies the rule to given file.
    19  func (r *UnhandledErrorRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
    20  	if r.ignoreList == nil {
    21  		r.ignoreList = make(ignoreListType, len(args))
    22  
    23  		for _, arg := range args {
    24  			argStr, ok := arg.(string)
    25  			if !ok {
    26  				panic(fmt.Sprintf("Invalid argument to the unhandled-error rule. Expecting a string, got %T", arg))
    27  			}
    28  
    29  			r.ignoreList[argStr] = struct{}{}
    30  		}
    31  	}
    32  
    33  	var failures []lint.Failure
    34  
    35  	walker := &lintUnhandledErrors{
    36  		ignoreList: r.ignoreList,
    37  		pkg:        file.Pkg,
    38  		onFailure: func(failure lint.Failure) {
    39  			failures = append(failures, failure)
    40  		},
    41  	}
    42  
    43  	file.Pkg.TypeCheck()
    44  	ast.Walk(walker, file.AST)
    45  
    46  	return failures
    47  }
    48  
    49  // Name returns the rule name.
    50  func (r *UnhandledErrorRule) Name() string {
    51  	return "unhandled-error"
    52  }
    53  
    54  type lintUnhandledErrors struct {
    55  	ignoreList ignoreListType
    56  	pkg        *lint.Package
    57  	onFailure  func(lint.Failure)
    58  }
    59  
    60  // Visit looks for statements that are function calls.
    61  // If the called function returns a value of type error a failure will be created.
    62  func (w *lintUnhandledErrors) Visit(node ast.Node) ast.Visitor {
    63  	switch n := node.(type) {
    64  	case *ast.ExprStmt:
    65  		fCall, ok := n.X.(*ast.CallExpr)
    66  		if !ok {
    67  			return nil // not a function call
    68  		}
    69  
    70  		funcType := w.pkg.TypeOf(fCall)
    71  		if funcType == nil {
    72  			return nil // skip, type info not available
    73  		}
    74  
    75  		switch t := funcType.(type) {
    76  		case *types.Named:
    77  			if !w.isTypeError(t) {
    78  				return nil // func call does not return an error
    79  			}
    80  
    81  			w.addFailure(fCall)
    82  		default:
    83  			retTypes, ok := funcType.Underlying().(*types.Tuple)
    84  			if !ok {
    85  				return nil // skip, unable to retrieve return type of the called function
    86  			}
    87  
    88  			if w.returnsAnError(retTypes) {
    89  				w.addFailure(fCall)
    90  			}
    91  		}
    92  	}
    93  	return w
    94  }
    95  
    96  func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) {
    97  	funcName := gofmt(n.Fun)
    98  	if _, mustIgnore := w.ignoreList[funcName]; mustIgnore {
    99  		return
   100  	}
   101  
   102  	w.onFailure(lint.Failure{
   103  		Category:   "bad practice",
   104  		Confidence: 1,
   105  		Node:       n,
   106  		Failure:    fmt.Sprintf("Unhandled error in call to function %v", funcName),
   107  	})
   108  }
   109  
   110  func (*lintUnhandledErrors) isTypeError(t *types.Named) bool {
   111  	const errorTypeName = "_.error"
   112  
   113  	return t.Obj().Id() == errorTypeName
   114  }
   115  
   116  func (w *lintUnhandledErrors) returnsAnError(tt *types.Tuple) bool {
   117  	for i := 0; i < tt.Len(); i++ {
   118  		nt, ok := tt.At(i).Type().(*types.Named)
   119  		if ok && w.isTypeError(nt) {
   120  			return true
   121  		}
   122  	}
   123  	return false
   124  }