Repository: mgechev/revive (go)

Issue:
context-as-argument is unsatisfiable sometimes
**Describe the bug**

The [context-as-argument](https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument) linter is unsatisfiable if a function accepts more than one context as parameters to the function.

**To Reproduce**

It is impossible to satisfy the linter with code like this (I haven't specifically tested this exact code block, but there's no way it _shouldn't_ trigger the linter...)

```go
func foo(ctx context.Context, other context.Context) {
    ...
}
```

```context-as-argument: context.Context should be the first parameter of a function```

**Expected behavior**

I would expect it to recognize that the first argument _is_ `ctx context.Context` and be satisfied. Beyond that, it would probably be okay if it checked that all the `context.Context` parameters are grouped at the beginning of the argument list, but that is a stylistic choice that I don't have a strong opinion on here.

**Additional context**

Sometimes it is useful to implement functionality like waiting on the expiration of either of two contexts. At the moment, I have to explicitly ignore this linter in those rare situations, simply because there is no way to make the linter happy here.

Pull request:
fix 525
Closes #525 

:warning: The rule `context-as-argument` is a rule that comes from, the defunct, `go-lint`. Therefore fixing this bug will make `revive` to behave slightly different from `go-lint`

(The failure message might also deserve to be adapted because now the rule checks that the _n_ parameters of type `context.Context` must be the _first n_ parameters of the function)

Patch:
diff --git a/rule/context-as-argument.go b/rule/context-as-argument.go
index 0ed28a8..6502a07 100644
--- a/rule/context-as-argument.go
+++ b/rule/context-as-argument.go
@@ -45,16 +45,19 @@ func (w lintContextArguments) Visit(n ast.Node) ast.Visitor {
 	}
 	// A context.Context should be the first parameter of a function.
 	// Flag any that show up after the first.
+	previousArgIsCtx := isPkgDot(fn.Type.Params.List[0].Type, "context", "Context")
 	for _, arg := range fn.Type.Params.List[1:] {
-		if isPkgDot(arg.Type, "context", "Context") {
+		argIsCtx := isPkgDot(arg.Type, "context", "Context")
+		if argIsCtx && !previousArgIsCtx {
 			w.onFailure(lint.Failure{
-				Node:       fn,
+				Node:       arg,
 				Category:   "arg-order",
 				Failure:    "context.Context should be the first parameter of a function",
 				Confidence: 0.9,
 			})
 			break // only flag one
 		}
+		previousArgIsCtx = argIsCtx
 	}
 	return w
 }


Test patch:
diff --git a/testdata/golint/context-as-argument.go b/testdata/golint/context-as-argument.go
index 5580e60..62e152c 100644
--- a/testdata/golint/context-as-argument.go
+++ b/testdata/golint/context-as-argument.go
@@ -22,3 +22,7 @@ func y(s string, ctx context.Context) { // MATCH /context.Context should be the
 // An invalid context.Context location with more than 2 args
 func y(s string, r int, ctx context.Context, x int) { // MATCH /context.Context should be the first parameter of a function/
 }
+
+func y(ctx1 context.Context, ctx2 context.Context, x int) {}
+
+func y(ctx1 context.Context, ctx2 context.Context, x int, ctx3 context.Context) {} // MATCH /context.Context should be the first parameter of a function/