Repository: mgechev/revive (go)

Issue:
error-strings does not allow multiple functions for the same package
**Describe the bug**
The `error-strings` rule uses the package name as the key to the [errorFunctions](https://github.com/mgechev/revive/blob/master/rule/error_strings.go#L46) map. If the arguments list has multiple functions under the same package like `a.Wrap` and `a.New`, the last function is linted against.

**To Reproduce**
Configure `error-strings` with error functions in the same package. e.g. `errors.New` will overwrite the default map.

**Expected behavior**
Allow multiple functions in the same package to be linted against.



Pull request description:
fix: error-strings custom funcs overwrites defaults
The PR fixes a bug in the `error-strings` rule where configuring custom functions overwrites default functions.

When configuring custom functions to `["errors.Wrap"]`, the internal map `r.errorFunctions` would be:

```go
	r.errorFunctions = map[string]map[string]struct{}{
		"fmt": {
			"Errorf": {},
		},
		"errors": {
			"Wrap": {},
		},
	}
```

but it should be:

```go
	r.errorFunctions = map[string]map[string]struct{}{
		"fmt": {
			"Errorf": {},
		},
		"errors": {
			"Errorf":       {},
			"WithMessage":  {},
			"Wrap":         {},
			"New":          {},
			"WithMessagef": {},
			"Wrapf":        {},
		},
	}
```

Fixes #1243

Patch:
diff --git a/rule/error_strings.go b/rule/error_strings.go
index 839a613..a5c2454 100644
--- a/rule/error_strings.go
+++ b/rule/error_strings.go
@@ -37,14 +37,19 @@ func (r *ErrorStringsRule) Configure(arguments lint.Arguments) error {
 
 	var invalidCustomFunctions []string
 	for _, argument := range arguments {
-		if functionName, ok := argument.(string); ok {
-			fields := strings.Split(strings.TrimSpace(functionName), ".")
-			if len(fields) != 2 || len(fields[0]) == 0 || len(fields[1]) == 0 {
-				invalidCustomFunctions = append(invalidCustomFunctions, functionName)
-				continue
-			}
-			r.errorFunctions[fields[0]] = map[string]struct{}{fields[1]: {}}
+		pkgFunction, ok := argument.(string)
+		if !ok {
+			continue
 		}
+		pkg, function, ok := strings.Cut(strings.TrimSpace(pkgFunction), ".")
+		if !ok || pkg == "" || function == "" {
+			invalidCustomFunctions = append(invalidCustomFunctions, pkgFunction)
+			continue
+		}
+		if _, ok := r.errorFunctions[pkg]; !ok {
+			r.errorFunctions[pkg] = map[string]struct{}{}
+		}
+		r.errorFunctions[pkg][function] = struct{}{}
 	}
 	if len(invalidCustomFunctions) != 0 {
 		return fmt.Errorf("found invalid custom function: %s", strings.Join(invalidCustomFunctions, ","))


Test patch:
diff --git a/rule/error_strings_test.go b/rule/error_strings_test.go
new file mode 100644
index 0000000..5362806
--- /dev/null
+++ b/rule/error_strings_test.go
@@ -0,0 +1,68 @@
+package rule_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/rule"
+)
+
+func TestErrorStringsRule_Configure(t *testing.T) {
+	tests := []struct {
+		name      string
+		arguments lint.Arguments
+		wantErr   error
+	}{
+		{
+			name:      "Default configuration",
+			arguments: lint.Arguments{},
+		},
+		{
+			name:      "Valid custom functions",
+			arguments: lint.Arguments{"mypkg.MyErrorFunc", "errors.New"},
+		},
+		{
+			name:      "Argument not a string",
+			arguments: lint.Arguments{123},
+		},
+		{
+			name:      "Invalid package",
+			arguments: lint.Arguments{".MyErrorFunc"},
+			wantErr:   errors.New("found invalid custom function: .MyErrorFunc"),
+		},
+		{
+			name:      "Invalid function",
+			arguments: lint.Arguments{"errors."},
+			wantErr:   errors.New("found invalid custom function: errors."),
+		},
+		{
+			name:      "Invalid custom function",
+			arguments: lint.Arguments{"invalidFunction"},
+			wantErr:   errors.New("found invalid custom function: invalidFunction"),
+		},
+		{
+			name:      "Mixed valid and invalid custom functions",
+			arguments: lint.Arguments{"mypkg.MyErrorFunc", "invalidFunction", "invalidFunction2"},
+			wantErr:   errors.New("found invalid custom function: invalidFunction,invalidFunction2"),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var r rule.ErrorStringsRule
+
+			err := r.Configure(tt.arguments)
+
+			if tt.wantErr == nil {
+				if err != nil {
+					t.Errorf("Configure() unexpected non-nil error %q", err)
+				}
+				return
+			}
+			if err == nil || err.Error() != tt.wantErr.Error() {
+				t.Errorf("Configure() unexpected error: got %q, want %q", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/test/error_strings_custom_functions_test.go b/test/error_strings_custom_functions_test.go
index 5cd67e4..255e06e 100644
--- a/test/error_strings_custom_functions_test.go
+++ b/test/error_strings_custom_functions_test.go
@@ -13,3 +13,10 @@ func TestErrorStringsWithCustomFunctions(t *testing.T) {
 		Arguments: args,
 	})
 }
+
+func TestErrorStringsIssue1243(t *testing.T) {
+	args := []any{"errors.Wrap"}
+	testRule(t, "error_strings_issue_1243", &rule.ErrorStringsRule{}, &lint.RuleConfig{
+		Arguments: args,
+	})
+}
diff --git a/testdata/error_strings_issue_1243.go b/testdata/error_strings_issue_1243.go
new file mode 100644
index 0000000..2e4d2c9
--- /dev/null
+++ b/testdata/error_strings_issue_1243.go
@@ -0,0 +1,11 @@
+package fixtures
+
+import (
+	"errors"
+	"fmt"
+)
+
+func issue1243() {
+	err := errors.New("An error occurred!") // MATCH /error strings should not be capitalized or end with punctuation or a newline/
+	fmt.Println(err)
+}