github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/deep-exit.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/mgechev/revive/lint"
     8  )
     9  
    10  // DeepExitRule lints program exit at functions other than main or init.
    11  type DeepExitRule struct{}
    12  
    13  // Apply applies the rule to given file.
    14  func (r *DeepExitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    15  	var failures []lint.Failure
    16  	onFailure := func(failure lint.Failure) {
    17  		failures = append(failures, failure)
    18  	}
    19  
    20  	var exitFunctions = map[string]map[string]bool{
    21  		"os":      {"Exit": true},
    22  		"syscall": {"Exit": true},
    23  		"log": {
    24  			"Fatal":   true,
    25  			"Fatalf":  true,
    26  			"Fatalln": true,
    27  			"Panic":   true,
    28  			"Panicf":  true,
    29  			"Panicln": true,
    30  		},
    31  	}
    32  
    33  	w := lintDeepExit{onFailure, exitFunctions, file.IsTest()}
    34  	ast.Walk(w, file.AST)
    35  	return failures
    36  }
    37  
    38  // Name returns the rule name.
    39  func (r *DeepExitRule) Name() string {
    40  	return "deep-exit"
    41  }
    42  
    43  type lintDeepExit struct {
    44  	onFailure     func(lint.Failure)
    45  	exitFunctions map[string]map[string]bool
    46  	isTestFile    bool
    47  }
    48  
    49  func (w lintDeepExit) Visit(node ast.Node) ast.Visitor {
    50  	if fd, ok := node.(*ast.FuncDecl); ok {
    51  		if w.mustIgnore(fd) {
    52  			return nil // skip analysis of this function
    53  		}
    54  
    55  		return w
    56  	}
    57  
    58  	se, ok := node.(*ast.ExprStmt)
    59  	if !ok {
    60  		return w
    61  	}
    62  	ce, ok := se.X.(*ast.CallExpr)
    63  	if !ok {
    64  		return w
    65  	}
    66  
    67  	fc, ok := ce.Fun.(*ast.SelectorExpr)
    68  	if !ok {
    69  		return w
    70  	}
    71  	id, ok := fc.X.(*ast.Ident)
    72  	if !ok {
    73  		return w
    74  	}
    75  
    76  	fn := fc.Sel.Name
    77  	pkg := id.Name
    78  	if w.exitFunctions[pkg] != nil && w.exitFunctions[pkg][fn] { // it's a call to an exit function
    79  		w.onFailure(lint.Failure{
    80  			Confidence: 1,
    81  			Node:       ce,
    82  			Category:   "bad practice",
    83  			Failure:    fmt.Sprintf("calls to %s.%s only in main() or init() functions", pkg, fn),
    84  		})
    85  	}
    86  
    87  	return w
    88  }
    89  
    90  func (w *lintDeepExit) mustIgnore(fd *ast.FuncDecl) bool {
    91  	fn := fd.Name.Name
    92  
    93  	return fn == "init" || fn == "main" || (w.isTestFile && fn == "TestMain")
    94  }