Repository: mgechev/revive
Pull request #859 (2023-08-07 16:30:26)

fix(deps): update module golang.org/x/tools to v0.12.0
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/tools | require | minor | `v0.11.1` -> `v0.12.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/mgechev/revive).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4yNy4xIiwidXBkYXRlZEluVmVyIjoiMzYuMjcuMSIsInRhcmdldEJyYW5jaCI6Im1hc3RlciJ9-->



Patch:
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index 1709188..3d78110 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -735,13 +735,36 @@ _Configuration_: N/A
 
 _Description_: This rule warns on unused parameters. Functions or methods with unused parameters can be a symptom of an unfinished refactoring or a bug.
 
-_Configuration_: N/A
+_Configuration_: Supports arguments with single of `map[string]any` with option `allowRegex` to provide additional to `_` mask to allowed unused parameter names, for example:
+
+```toml
+[rule.unused-parameter]
+    Arguments = [ { allowRegex = "^_" } ]
+```
+
+allows any names started with `_`, not just `_` itself:
+
+```go
+func SomeFunc(_someObj *MyStruct) {} // matches rule
+```
 
 ## unused-receiver
 
 _Description_: This rule warns on unused method receivers. Methods with unused receivers can be a symptom of an unfinished refactoring or a bug.
 
-_Configuration_: N/A
+_Configuration_: Supports arguments with single of `map[string]any` with option `allowRegex` to provide additional to `_` mask to allowed unused receiver names, for example:
+
+```toml
+[rule.unused-receiver]
+    Arguments = [ { allowRegex = "^_" } ]
+```
+
+allows any names started with `_`, not just `_` itself:
+
+```go
+func (_my *MyStruct) SomeMethod() {} // matches rule
+```
+
 
 ## use-any
 
diff --git a/go.mod b/go.mod
index 5228b32..a7e990c 100644
--- a/go.mod
+++ b/go.mod
@@ -11,12 +11,12 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/pkg/errors v0.9.1
-	golang.org/x/tools v0.11.1
+	golang.org/x/tools v0.12.0
 )
 
 require (
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.17 // indirect
 	github.com/mattn/go-runewidth v0.0.9 // indirect
-	golang.org/x/sys v0.10.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 946dadb..f8603b6 100644
--- a/go.sum
+++ b/go.sum
@@ -83,6 +83,8 @@ golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
 golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
@@ -107,6 +109,8 @@ golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
 golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
 golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
 golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
+golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
+golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/rule/imports-blacklist.go b/rule/imports-blacklist.go
index 7106628..bb8262c 100644
--- a/rule/imports-blacklist.go
+++ b/rule/imports-blacklist.go
@@ -52,10 +52,6 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments)
 
 	var failures []lint.Failure
 
-	if file.IsTest() {
-		return failures // skip, test file
-	}
-
 	for _, is := range file.AST.Imports {
 		path := is.Path
 		if path != nil && r.isBlacklisted(path.Value) {
diff --git a/rule/unused-param.go b/rule/unused-param.go
index ab3da45..df6cd9a 100644
--- a/rule/unused-param.go
+++ b/rule/unused-param.go
@@ -3,22 +3,72 @@ package rule
 import (
 	"fmt"
 	"go/ast"
+	"regexp"
+	"sync"
 
 	"github.com/mgechev/revive/lint"
 )
 
 // UnusedParamRule lints unused params in functions.
-type UnusedParamRule struct{}
+type UnusedParamRule struct {
+	configured bool
+	// regex to check if some name is valid for unused parameter, "^_$" by default
+	allowRegex *regexp.Regexp
+	failureMsg string
+	sync.Mutex
+}
+
+func (r *UnusedParamRule) configure(args lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
+
+	if r.configured {
+		return
+	}
+	r.configured = true
+
+	// while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives
+	// it's more compatible to JSON nature of configurations
+	var allowedRegexStr string
+	if len(args) == 0 {
+		allowedRegexStr = "^_$"
+		r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _"
+	} else {
+		// Arguments = [{}]
+		options := args[0].(map[string]interface{})
+		// Arguments = [{allowedRegex="^_"}]
+
+		if allowedRegexParam, ok := options["allowRegex"]; ok {
+			allowedRegexStr, ok = allowedRegexParam.(string)
+			if !ok {
+				panic(fmt.Errorf("error configuring %s rule: allowedRegex is not string but [%T]", r.Name(), allowedRegexParam))
+			}
+		}
+	}
+	var err error
+	r.allowRegex, err = regexp.Compile(allowedRegexStr)
+	if err != nil {
+		panic(fmt.Errorf("error configuring %s rule: allowedRegex is not valid regex [%s]: %v", r.Name(), allowedRegexStr, err))
+	}
+
+	if r.failureMsg == "" {
+		r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it to match " + r.allowRegex.String()
+	}
+}
 
 // Apply applies the rule to given file.
-func (*UnusedParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+func (r *UnusedParamRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
+	r.configure(args)
 	var failures []lint.Failure
 
 	onFailure := func(failure lint.Failure) {
 		failures = append(failures, failure)
 	}
-
-	w := lintUnusedParamRule{onFailure: onFailure}
+	w := lintUnusedParamRule{
+		onFailure:  onFailure,
+		allowRegex: r.allowRegex,
+		failureMsg: r.failureMsg,
+	}
 
 	ast.Walk(w, file.AST)
 
@@ -31,7 +81,9 @@ func (*UnusedParamRule) Name() string {
 }
 
 type lintUnusedParamRule struct {
-	onFailure func(lint.Failure)
+	onFailure  func(lint.Failure)
+	allowRegex *regexp.Regexp
+	failureMsg string
 }
 
 func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
@@ -65,12 +117,15 @@ func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
 
 		for _, p := range n.Type.Params.List {
 			for _, n := range p.Names {
+				if w.allowRegex.FindStringIndex(n.Name) != nil {
+					continue
+				}
 				if params[n.Obj] {
 					w.onFailure(lint.Failure{
 						Confidence: 1,
 						Node:       n,
 						Category:   "bad practice",
-						Failure:    fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", n.Name),
+						Failure:    fmt.Sprintf(w.failureMsg, n.Name),
 					})
 				}
 			}
diff --git a/rule/unused-receiver.go b/rule/unused-receiver.go
index 2289a51..488572b 100644
--- a/rule/unused-receiver.go
+++ b/rule/unused-receiver.go
@@ -3,22 +3,72 @@ package rule
 import (
 	"fmt"
 	"go/ast"
+	"regexp"
+	"sync"
 
 	"github.com/mgechev/revive/lint"
 )
 
 // UnusedReceiverRule lints unused params in functions.
-type UnusedReceiverRule struct{}
+type UnusedReceiverRule struct {
+	configured bool
+	// regex to check if some name is valid for unused parameter, "^_$" by default
+	allowRegex *regexp.Regexp
+	failureMsg string
+	sync.Mutex
+}
+
+func (r *UnusedReceiverRule) configure(args lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
+
+	if r.configured {
+		return
+	}
+	r.configured = true
+
+	// while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives
+	// it's more compatible to JSON nature of configurations
+	var allowedRegexStr string
+	if len(args) == 0 {
+		allowedRegexStr = "^_$"
+		r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _"
+	} else {
+		// Arguments = [{}]
+		options := args[0].(map[string]interface{})
+		// Arguments = [{allowedRegex="^_"}]
+
+		if allowedRegexParam, ok := options["allowRegex"]; ok {
+			allowedRegexStr, ok = allowedRegexParam.(string)
+			if !ok {
+				panic(fmt.Errorf("error configuring [unused-receiver] rule: allowedRegex is not string but [%T]", allowedRegexParam))
+			}
+		}
+	}
+	var err error
+	r.allowRegex, err = regexp.Compile(allowedRegexStr)
+	if err != nil {
+		panic(fmt.Errorf("error configuring [unused-receiver] rule: allowedRegex is not valid regex [%s]: %v", allowedRegexStr, err))
+	}
+	if r.failureMsg == "" {
+		r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match " + r.allowRegex.String()
+	}
+}
 
 // Apply applies the rule to given file.
-func (*UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+func (r *UnusedReceiverRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
+	r.configure(args)
 	var failures []lint.Failure
 
 	onFailure := func(failure lint.Failure) {
 		failures = append(failures, failure)
 	}
 
-	w := lintUnusedReceiverRule{onFailure: onFailure}
+	w := lintUnusedReceiverRule{
+		onFailure:  onFailure,
+		allowRegex: r.allowRegex,
+		failureMsg: r.failureMsg,
+	}
 
 	ast.Walk(w, file.AST)
 
@@ -31,7 +81,9 @@ func (*UnusedReceiverRule) Name() string {
 }
 
 type lintUnusedReceiverRule struct {
-	onFailure func(lint.Failure)
+	onFailure  func(lint.Failure)
+	allowRegex *regexp.Regexp
+	failureMsg string
 }
 
 func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor {
@@ -51,6 +103,10 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor {
 			return nil // the receiver is already named _
 		}
 
+		if w.allowRegex != nil && w.allowRegex.FindStringIndex(recID.Name) != nil {
+			return nil
+		}
+
 		// inspect the func body looking for references to the receiver id
 		fselect := func(n ast.Node) bool {
 			ident, isAnID := n.(*ast.Ident)
@@ -67,7 +123,7 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor {
 			Confidence: 1,
 			Node:       recID,
 			Category:   "bad practice",
-			Failure:    fmt.Sprintf("method receiver '%s' is not referenced in method's body, consider removing or renaming it as _", recID.Name),
+			Failure:    fmt.Sprintf(w.failureMsg, recID.Name),
 		})
 
 		return nil // full method body already inspected


Test patch:
diff --git a/test/unused-param_test.go b/test/unused-param_test.go
index 7b6472c..8be1494 100644
--- a/test/unused-param_test.go
+++ b/test/unused-param_test.go
@@ -3,11 +3,15 @@ package test
 import (
 	"testing"
 
+	"github.com/mgechev/revive/lint"
 	"github.com/mgechev/revive/rule"
 )
 
 func TestUnusedParam(t *testing.T) {
 	testRule(t, "unused-param", &rule.UnusedParamRule{})
+	testRule(t, "unused-param-custom-regex", &rule.UnusedParamRule{}, &lint.RuleConfig{Arguments: []interface{}{
+		map[string]interface{}{"allowRegex": "^xxx"},
+	}})
 }
 
 func BenchmarkUnusedParam(b *testing.B) {
diff --git a/test/unused-receiver_test.go b/test/unused-receiver_test.go
index e4bae7c..2fb6a0e 100644
--- a/test/unused-receiver_test.go
+++ b/test/unused-receiver_test.go
@@ -3,9 +3,13 @@ package test
 import (
 	"testing"
 
+	"github.com/mgechev/revive/lint"
 	"github.com/mgechev/revive/rule"
 )
 
 func TestUnusedReceiver(t *testing.T) {
 	testRule(t, "unused-receiver", &rule.UnusedReceiverRule{})
+	testRule(t, "unused-receiver-custom-regex", &rule.UnusedReceiverRule{}, &lint.RuleConfig{Arguments: []interface{}{
+		map[string]interface{}{"allowRegex": "^xxx"},
+	}})
 }
diff --git a/testdata/unused-param-custom-regex.go b/testdata/unused-param-custom-regex.go
new file mode 100644
index 0000000..24612ae
--- /dev/null
+++ b/testdata/unused-param-custom-regex.go
@@ -0,0 +1,12 @@
+package fixtures
+
+// all will ok with xxxParam if Arguments = [{allowRegex="^xxx"}]
+
+func f0(xxxParam int) {}
+
+// still works with _
+
+func f1(_ int) {}
+
+func f2(yyyParam int) { // MATCH /parameter 'yyyParam' seems to be unused, consider removing or renaming it to match ^xxx/
+}
diff --git a/testdata/unused-receiver-custom-regex.go b/testdata/unused-receiver-custom-regex.go
new file mode 100644
index 0000000..3fdcd4a
--- /dev/null
+++ b/testdata/unused-receiver-custom-regex.go
@@ -0,0 +1,12 @@
+package fixtures
+
+// all will ok with xxxParam if Arguments = [{allowRegex="^xxx"}]
+
+func (xxxParam *SomeObj) f0() {}
+
+// still works with _
+
+func (_ *SomeObj) f1() {}
+
+func (yyyParam *SomeObj) f2() { // MATCH /method receiver 'yyyParam' is not referenced in method's body, consider removing or renaming it to match ^xxx/
+}