github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/context-as-argument.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"strings"
     7  
     8  	"github.com/songshiyun/revive/lint"
     9  )
    10  
    11  // ContextAsArgumentRule lints given else constructs.
    12  type ContextAsArgumentRule struct {
    13  	allowTypesLUT map[string]struct{}
    14  }
    15  
    16  // Apply applies the rule to given file.
    17  func (r *ContextAsArgumentRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
    18  	if r.allowTypesLUT == nil {
    19  		r.allowTypesLUT = getAllowTypesFromArguments(args)
    20  	}
    21  
    22  	var failures []lint.Failure
    23  	walker := lintContextArguments{
    24  		allowTypesLUT: r.allowTypesLUT,
    25  		onFailure: func(failure lint.Failure) {
    26  			failures = append(failures, failure)
    27  		},
    28  	}
    29  
    30  	ast.Walk(walker, file.AST)
    31  
    32  	return failures
    33  }
    34  
    35  // Name returns the rule name.
    36  func (r *ContextAsArgumentRule) Name() string {
    37  	return "context-as-argument"
    38  }
    39  
    40  type lintContextArguments struct {
    41  	allowTypesLUT map[string]struct{}
    42  	onFailure     func(lint.Failure)
    43  }
    44  
    45  func (w lintContextArguments) Visit(n ast.Node) ast.Visitor {
    46  	fn, ok := n.(*ast.FuncDecl)
    47  	if !ok || len(fn.Type.Params.List) <= 1 {
    48  		return w
    49  	}
    50  
    51  	fnArgs := fn.Type.Params.List
    52  
    53  	// A context.Context should be the first parameter of a function.
    54  	// Flag any that show up after the first.
    55  	isCtxStillAllowed := true
    56  	for _, arg := range fnArgs {
    57  		argIsCtx := isPkgDot(arg.Type, "context", "Context")
    58  		if argIsCtx && !isCtxStillAllowed {
    59  			w.onFailure(lint.Failure{
    60  				Node:       arg,
    61  				Category:   "arg-order",
    62  				Failure:    "context.Context should be the first parameter of a function",
    63  				Confidence: 0.9,
    64  			})
    65  			break // only flag one
    66  		}
    67  
    68  		typeName := gofmt(arg.Type)
    69  		// a parameter of type context.Context is still allowed if the current arg type is in the LUT
    70  		_, isCtxStillAllowed = w.allowTypesLUT[typeName]
    71  	}
    72  
    73  	return nil // avoid visiting the function body
    74  }
    75  
    76  func getAllowTypesFromArguments(args lint.Arguments) map[string]struct{} {
    77  	allowTypesBefore := []string{}
    78  	if len(args) >= 1 {
    79  		argKV, ok := args[0].(map[string]interface{})
    80  		if !ok {
    81  			panic(fmt.Sprintf("Invalid argument to the context-as-argument rule. Expecting a k,v map, got %T", args[0]))
    82  		}
    83  		for k, v := range argKV {
    84  			switch k {
    85  			case "allowTypesBefore":
    86  				typesBefore, ok := v.(string)
    87  				if !ok {
    88  					panic(fmt.Sprintf("Invalid argument to the context-as-argument.allowTypesBefore rule. Expecting a string, got %T", v))
    89  				}
    90  				allowTypesBefore = append(allowTypesBefore, strings.Split(typesBefore, ",")...)
    91  			default:
    92  				panic(fmt.Sprintf("Invalid argument to the context-as-argument rule. Unrecognized key %s", k))
    93  			}
    94  		}
    95  	}
    96  
    97  	result := make(map[string]struct{}, len(allowTypesBefore))
    98  	for _, v := range allowTypesBefore {
    99  		result[v] = struct{}{}
   100  	}
   101  
   102  	result["context.Context"] = struct{}{} // context.Context is always allowed before another context.Context
   103  	return result
   104  }