Repository: mgechev/revive (go)

Issue:
Add the receiver name length check to `rule.receiver-naming`
**Is your feature request related to a problem? Please describe.**
I would like to check the length of receiver variable names.

**Describe the solution you'd like**
Add the receiver variable name length check to the `receiver-naming` rule.

**Describe alternatives you've considered**
I created a [linter](https://github.com/ytnsym/receivernamelen), but it seemed like it would be better to improve the `receiver-naming` rule.

**Additional context**
https://github.com/golangci/golangci-lint/pull/5007#issuecomment-2350457699


Pull request description:
adds check for receiver names length
Closes #1040 
The rule receiver-naming now accepts an optional argument indicating the maximum length of receiver names. If no argument is provided the rule does not check for names length. 

Patch:
diff --git a/README.md b/README.md
index 81e247d..051d789 100644
--- a/README.md
+++ b/README.md
@@ -480,7 +480,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
 | [`var-naming`](./RULES_DESCRIPTIONS.md#var-naming)          |  allowlist & blocklist of initialisms   | Naming rules.                                                    |   yes    |  no   |
 | [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments)    |  n/a   | Package commenting conventions.                                  |   yes    |  no   |
 | [`range`](./RULES_DESCRIPTIONS.md#range)               |  n/a   | Prevents redundant variables when iterating over a collection.   |   yes    |  no   |
-| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming)     |  n/a   | Conventions around the naming of receivers.                      |   yes    |  no   |
+| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming)     |  map (optional)   | Conventions around the naming of receivers.                      |   yes    |  no   |
 | [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)   |  []string   | Prevents redundant else statements.                              |   yes    |  no   |
 | [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit)      |  int (defaults to 8)  | Specifies the maximum number of arguments a function can receive |    no    |  no   |
 | [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic)          |  int (defaults to 10)   | Sets restriction for maximum Cyclomatic complexity.              |    no    |  no   |
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index 4e27930..9b18df6 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -737,7 +737,14 @@ _Configuration_: N/A
 
 _Description_: By convention, receiver names in a method should reflect their identity. For example, if the receiver is of type `Parts`, `p` is an adequate name for it. Contrary to other languages, it is not idiomatic to name receivers as `this` or `self`.
 
-_Configuration_: N/A
+_Configuration_: (optional) list of key-value-pair-map (`[]map[string]any`).
+
+- `maxLength` : (int) max length of receiver name
+
+```toml
+[rule.receiver-naming]
+    arguments = [{maxLength=2}]
+```
 
 ## redefines-builtin-id
 
diff --git a/rule/receiver-naming.go b/rule/receiver-naming.go
index d79bb9f..afcd99b 100644
--- a/rule/receiver-naming.go
+++ b/rule/receiver-naming.go
@@ -3,16 +3,55 @@ package rule
 import (
 	"fmt"
 	"go/ast"
+	"sync"
 
 	"github.com/mgechev/revive/internal/typeparams"
 	"github.com/mgechev/revive/lint"
 )
 
 // ReceiverNamingRule lints given else constructs.
-type ReceiverNamingRule struct{}
+type ReceiverNamingRule struct {
+	receiverNameMaxLength int
+	sync.Mutex
+}
+
+const defaultReceiverNameMaxLength = -1 // thus will not check
+
+func (r *ReceiverNamingRule) configure(arguments lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
+	if r.receiverNameMaxLength != 0 {
+		return
+	}
+
+	r.receiverNameMaxLength = defaultReceiverNameMaxLength
+	if len(arguments) < 1 {
+		return
+	}
+
+	args, ok := arguments[0].(map[string]any)
+	if !ok {
+		panic(fmt.Sprintf("Unable to get arguments for rule %s. Expected object of key-value-pairs.", r.Name()))
+	}
+
+	for k, v := range args {
+		switch k {
+		case "maxLength":
+			value, ok := v.(int64)
+			if !ok {
+				panic(fmt.Sprintf("Invalid value %v for argument %s of rule %s, expected integer value got %T", v, k, r.Name(), v))
+			}
+			r.receiverNameMaxLength = int(value)
+		default:
+			panic(fmt.Sprintf("Unknown argument %s for %s rule.", k, r.Name()))
+		}
+	}
+}
 
 // Apply applies the rule to given file.
-func (*ReceiverNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+func (r *ReceiverNamingRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
+	r.configure(args)
+
 	var failures []lint.Failure
 
 	fileAst := file.AST
@@ -20,7 +59,8 @@ func (*ReceiverNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failu
 		onFailure: func(failure lint.Failure) {
 			failures = append(failures, failure)
 		},
-		typeReceiver: map[string]string{},
+		typeReceiver:          map[string]string{},
+		receiverNameMaxLength: r.receiverNameMaxLength,
 	}
 
 	ast.Walk(walker, fileAst)
@@ -34,8 +74,9 @@ func (*ReceiverNamingRule) Name() string {
 }
 
 type lintReceiverName struct {
-	onFailure    func(lint.Failure)
-	typeReceiver map[string]string
+	onFailure             func(lint.Failure)
+	typeReceiver          map[string]string
+	receiverNameMaxLength int
 }
 
 func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
@@ -66,6 +107,17 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
 		})
 		return w
 	}
+
+	if w.receiverNameMaxLength > 0 && len([]rune(name)) > w.receiverNameMaxLength {
+		w.onFailure(lint.Failure{
+			Node:       n,
+			Confidence: 1,
+			Category:   "naming",
+			Failure:    fmt.Sprintf("receiver name %s is longer than %d characters", name, w.receiverNameMaxLength),
+		})
+		return w
+	}
+
 	recv := typeparams.ReceiverType(fn)
 	if prev, ok := w.typeReceiver[recv]; ok && prev != name {
 		w.onFailure(lint.Failure{


Test patch:
diff --git a/test/receiver-naming_test.go b/test/receiver-naming_test.go
index 1d530cb..bbab89a 100644
--- a/test/receiver-naming_test.go
+++ b/test/receiver-naming_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/mgechev/revive/internal/typeparams"
+	"github.com/mgechev/revive/lint"
 	"github.com/mgechev/revive/rule"
 )
 
@@ -13,3 +14,11 @@ func TestReceiverNamingTypeParams(t *testing.T) {
 	}
 	testRule(t, "receiver-naming-issue-669", &rule.ReceiverNamingRule{})
 }
+
+func TestReceiverNamingMaxLength(t *testing.T) {
+	args := []any{map[string]any{
+		"maxLength": int64(2),
+	}}
+	testRule(t, "receiver-naming-issue-1040", &rule.ReceiverNamingRule{},
+		&lint.RuleConfig{Arguments: args})
+}
diff --git a/testdata/receiver-naming-issue-1040.go b/testdata/receiver-naming-issue-1040.go
new file mode 100644
index 0000000..ab69dfa
--- /dev/null
+++ b/testdata/receiver-naming-issue-1040.go
@@ -0,0 +1,5 @@
+package fixtures
+
+func (o *o) f1()     {}
+func (tw *tw) f2()   {}
+func (thr *thr) f3() {} // MATCH /receiver name thr is longer than 2 characters/