Repository: mgechev/revive (go)

Issue:
error-strings function list should be user configurable
**Is your feature request related to a problem? Please describe.**

Our code-base has error strings that are not being checked by `error-strings`.

**Describe the solution you'd like**

Currently, `error-strings` uses a [hardcoded list](https://github.com/mgechev/revive/blob/04728cf0de86ab99fb3327a1ea48f7d51a411352/rule/error-strings.go#L20-L32) of functions that it checks. We use an internal package for generating errors that is not covered by the list. It would be nice there was a configuration option to add additional functions.

**Describe alternatives you've considered**

Potentially, we could write our own Ruleguard rule or use another linting package. We could also rename our internal package to `errors`, which I think would end up working given the current error functions we have and my understanding of how `error-strings` works.


Pull request description:
Allow to customize user functions in rule `error-strings`
**Please, describe in details what's your motivation for this PR**
As mentioned in #697, we may use other error packages instead of builtin or pkg
and also want to lint those error messages with `revive`, so it will be good to
allow to customize the function name.

### CHECKLIST
- [x] Did you add tests?
- [x] Does your code follows the coding style of the rest of the repository?
- [ ] Does the Travis build passes?

Closes #697 


Patch:
diff --git a/README.md b/README.md
index 49f1730..94bd4da 100644
--- a/README.md
+++ b/README.md
@@ -350,6 +350,8 @@ enableAllRules = true
     Arguments = [7]
 [rule.function-result-limit]
     Arguments = [3]
+[rule.error-strings]
+    Arguments = ["mypackage.Error"]
 ```
 
 ### Default Configuration
@@ -410,7 +412,6 @@ warningCode = 0
 ## Available Rules
 
 List of all available rules. The rules ported from `golint` are left unchanged and indicated in the `golint` column.
-
 | Name                  | Config | Description                                                      | `golint` | Typed |
 | --------------------- | :----: | :--------------------------------------------------------------- | :------: | :---: |
 | [`context-keys-type`](./RULES_DESCRIPTIONS.md#context-key-types)   |  n/a   | Disallows the usage of basic types in `context.WithValue`.       |   yes    |  yes  |
@@ -423,7 +424,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
 | [`context-as-argument`](./RULES_DESCRIPTIONS.md#context-as-argument) |  n/a   | `context.Context` should be the first argument of a function.    |   yes    |  no   |
 | [`dot-imports`](./RULES_DESCRIPTIONS.md#dot-imports)         |  n/a   | Forbids `.` imports.                                             |   yes    |  no   |
 | [`error-return`](./RULES_DESCRIPTIONS.md#error-return)        |  n/a   | The error return parameter should be last.                       |   yes    |  no   |
-| [`error-strings`](./RULES_DESCRIPTIONS.md#error-strings)       |  n/a   | Conventions around error strings.                                |   yes    |  no   |
+| [`error-strings`](./RULES_DESCRIPTIONS.md#error-strings)       |  []string   | Conventions around error strings.                                |   yes    |  no   |
 | [`error-naming`](./RULES_DESCRIPTIONS.md#error-naming)        |  n/a   | Naming of error variables.                                       |   yes    |  no   |
 | [`exported`](./RULES_DESCRIPTIONS.md#exported)            |  []string   | Naming and commenting conventions on exported symbols.           |   yes    |  no   |
 | [`if-return`](./RULES_DESCRIPTIONS.md#if-return)           |  n/a   | Redundant if when returning an error.                            |   no    |  no   |
diff --git a/go.mod b/go.mod
index a37f863..acab9a3 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
 	github.com/BurntSushi/toml v1.1.0
-	github.com/chavacava/garif v0.0.0-20220624065725-d6fd61e6a499
+	github.com/chavacava/garif v0.0.0-20220630083739-93517212f375
 	github.com/fatih/color v1.13.0
 	github.com/fatih/structtag v1.2.0
 	github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517
diff --git a/go.sum b/go.sum
index 6e9b1d9..50f10ab 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ github.com/chavacava/garif v0.0.0-20220608063557-908ad76c2c48 h1:fGu6x/t2goOrSv+
 github.com/chavacava/garif v0.0.0-20220608063557-908ad76c2c48/go.mod h1:4WaA5vrs1EZSrRpOkp6W5szFQp2/FJjNj/TJEIsKmgs=
 github.com/chavacava/garif v0.0.0-20220624065725-d6fd61e6a499 h1:AzpcMkb+5iIvI6yOo/CHeHpArnx0MMVWDIm7ZCJ3fY4=
 github.com/chavacava/garif v0.0.0-20220624065725-d6fd61e6a499/go.mod h1:TeliYfWi9CH5NeRxIq3FllEVpbBNB1ULPvEv9rT99AY=
+github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 h1:E7LT642ysztPWE0dfz43cWOvMiF42DyTRC+eZIaO4yI=
+github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,6 +43,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
diff --git a/rule/error-strings.go b/rule/error-strings.go
index 9e405c2..81ebda5 100644
--- a/rule/error-strings.go
+++ b/rule/error-strings.go
@@ -4,6 +4,8 @@ import (
 	"go/ast"
 	"go/token"
 	"strconv"
+	"strings"
+	"sync"
 	"unicode"
 	"unicode/utf8"
 
@@ -11,13 +13,20 @@ import (
 )
 
 // ErrorStringsRule lints given else constructs.
-type ErrorStringsRule struct{}
+type ErrorStringsRule struct {
+	errorFunctions map[string]map[string]struct{}
+	sync.Mutex
+}
 
-// Apply applies the rule to given file.
-func (*ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
-	var failures []lint.Failure
+func (r *ErrorStringsRule) configure(arguments lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
 
-	errorFunctions := map[string]map[string]struct{}{
+	if r.errorFunctions != nil {
+		return
+	}
+
+	r.errorFunctions = map[string]map[string]struct{}{
 		"fmt": {
 			"Errorf": {},
 		},
@@ -31,11 +40,33 @@ func (*ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
 		},
 	}
 
+	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]: {}}
+		}
+	}
+	if len(invalidCustomFunctions) != 0 {
+		panic("found invalid custom function: " + strings.Join(invalidCustomFunctions, ","))
+	}
+}
+
+// Apply applies the rule to given file.
+func (r *ErrorStringsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
+	var failures []lint.Failure
+
+	r.configure(arguments)
+
 	fileAst := file.AST
 	walker := lintErrorStrings{
 		file:           file,
 		fileAst:        fileAst,
-		errorFunctions: errorFunctions,
+		errorFunctions: r.errorFunctions,
 		onFailure: func(failure lint.Failure) {
 			failures = append(failures, failure)
 		},


Test patch:
diff --git a/test/error-strings-custom-functions_test.go b/test/error-strings-custom-functions_test.go
new file mode 100644
index 0000000..afd1833
--- /dev/null
+++ b/test/error-strings-custom-functions_test.go
@@ -0,0 +1,15 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/rule"
+)
+
+func TestErrorStringsWithCustomFunctions(t *testing.T) {
+	args := []interface{}{"pkgErrors.Wrap"}
+	testRule(t, "error-strings-with-custom-functions", &rule.ErrorStringsRule{}, &lint.RuleConfig{
+		Arguments: args,
+	})
+}
diff --git a/testdata/error-strings-with-custom-functions.go b/testdata/error-strings-with-custom-functions.go
new file mode 100644
index 0000000..ec8a405
--- /dev/null
+++ b/testdata/error-strings-with-custom-functions.go
@@ -0,0 +1,12 @@
+package fixtures
+
+import (
+	pkgErrors "github.com/pkg/errors"
+)
+
+// Check for the error strings themselves.
+
+func errorsStrings(x int) error {
+	var err error
+	return pkgErrors.Wrap(err, "This %d is too low") // MATCH /error strings should not be capitalized or end with punctuation or a newline/
+}