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 }