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

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/songshiyun/revive/lint"
     8  )
     9  
    10  // FlagParamRule lints given else constructs.
    11  type FlagParamRule struct{}
    12  
    13  // Apply applies the rule to given file.
    14  func (r *FlagParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    15  	var failures []lint.Failure
    16  
    17  	onFailure := func(failure lint.Failure) {
    18  		failures = append(failures, failure)
    19  	}
    20  
    21  	w := lintFlagParamRule{onFailure: onFailure}
    22  	ast.Walk(w, file.AST)
    23  	return failures
    24  }
    25  
    26  // Name returns the rule name.
    27  func (r *FlagParamRule) Name() string {
    28  	return "flag-parameter"
    29  }
    30  
    31  type lintFlagParamRule struct {
    32  	onFailure func(lint.Failure)
    33  }
    34  
    35  func (w lintFlagParamRule) Visit(node ast.Node) ast.Visitor {
    36  	fd, ok := node.(*ast.FuncDecl)
    37  	if !ok {
    38  		return w
    39  	}
    40  
    41  	if fd.Body == nil {
    42  		return nil // skip whole function declaration
    43  	}
    44  
    45  	for _, p := range fd.Type.Params.List {
    46  		t := p.Type
    47  
    48  		id, ok := t.(*ast.Ident)
    49  		if !ok {
    50  			continue
    51  		}
    52  
    53  		if id.Name != "bool" {
    54  			continue
    55  		}
    56  
    57  		cv := conditionVisitor{p.Names, fd, w}
    58  		ast.Walk(cv, fd.Body)
    59  	}
    60  
    61  	return w
    62  }
    63  
    64  type conditionVisitor struct {
    65  	ids    []*ast.Ident
    66  	fd     *ast.FuncDecl
    67  	linter lintFlagParamRule
    68  }
    69  
    70  func (w conditionVisitor) Visit(node ast.Node) ast.Visitor {
    71  	ifStmt, ok := node.(*ast.IfStmt)
    72  	if !ok {
    73  		return w
    74  	}
    75  
    76  	fselect := func(n ast.Node) bool {
    77  		ident, ok := n.(*ast.Ident)
    78  		if !ok {
    79  			return false
    80  		}
    81  
    82  		for _, id := range w.ids {
    83  			if ident.Name == id.Name {
    84  				return true
    85  			}
    86  		}
    87  
    88  		return false
    89  	}
    90  
    91  	uses := pick(ifStmt.Cond, fselect, nil)
    92  
    93  	if len(uses) < 1 {
    94  		return w
    95  	}
    96  
    97  	w.linter.onFailure(lint.Failure{
    98  		Confidence: 1,
    99  		Node:       w.fd.Type.Params,
   100  		Category:   "bad practice",
   101  		Failure:    fmt.Sprintf("parameter '%s' seems to be a control flag, avoid control coupling", uses[0]),
   102  	})
   103  
   104  	return nil
   105  }