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