Repository: mgechev/revive (go)

Issue:
Duplicated findings for `string-format` when there is more than one scope provided for single argument
**Describe the bug**
For `string-format` rule, when there is more than one scope provided for single argument there are duplicated findings in the results.
The number of findings is the same as the number of scopes provided in the configuration.

**To Reproduce**
Steps to reproduce the behavior:
1. I updated revive `go install github.com/mgechev/revive@latest`
2. I run it with the following flags & configuration file:

```shell
# flags
revive -config revive.toml msg.go
```

```toml
# config file
[rule.string-format]
    arguments = [
        ["fmt.Errorf[0],errors.New[0]", "/^([^A-Z]|$)/", "must not start with a capital letter"],
    ]
```

`msg.go`:
```go
package main

import (
	"errors"
)

func ReturnError() error {
	return errors.New("This is an error.")
}
```

**Expected behavior**
A single finding is found.

**Expected behavior**
Two findings are there:
```
msg.go:8:20: must not start with a capital letter
msg.go:8:20: must not start with a capital letter
```


**Desktop (please complete the following information):**
 - OS: `Ubuntu 23.10`
 - Version of Go: `1.23.0`


Pull request description:
fix duplicated findings for string-format when there is more than one…
Closes #1063 + minor code refactoring

Patch:
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index ee880f9..850c70c 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -16,3 +16,5 @@ jobs:
 
     - name: Run Revive Action
       uses: morphy2k/revive-action@v2
+      with:
+        config: revive.toml
diff --git a/README.md b/README.md
index f3032c9..a661e31 100644
--- a/README.md
+++ b/README.md
@@ -548,6 +548,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
 | [`max-control-nesting`](./RULES_DESCRIPTIONS.md#max-control-nesting) |  int (defaults to 5)  | Sets restriction for maximum nesting of control structures. |    no    |  no   |
 | [`comments-density`](./RULES_DESCRIPTIONS.md#comments-density) |  int (defaults to 0)  | Enforces a minimum comment / code relation |    no    |  no   |
 | [`file-length-limit`](./RULES_DESCRIPTIONS.md#file-length-limit) | map (optional)| Enforces a maximum number of lines per file |    no    |  no   |
+| [`filename-format`](./RULES_DESCRIPTIONS.md#filename-format) | regular expression (optional) | Enforces the formatting of filenames |   no    |  no   |
 
 ## Configurable rules
 
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index 3ef2acd..fcd4e68 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -39,6 +39,7 @@ List of all available rules.
   - [exported](#exported)
   - [file-header](#file-header)
   - [file-length-limit](#file-length-limit)
+  - [filename-format](#filename-format)
   - [flag-parameter](#flag-parameter)
   - [function-length](#function-length)
   - [function-result-limit](#function-result-limit)
@@ -519,6 +520,18 @@ Example:
   arguments = [{max=100,skipComments=true,skipBlankLines=true}]
 ```
 
+## filename-format
+_Description_: enforces conventions on source file names. By default, the rule enforces filenames of the form `^[_A-Za-z0-9][_A-Za-z0-9-]*.go$`: Optionally, the rule can be configured to enforce other forms.
+
+_Configuration_: (string) regular expression for source filenames.
+
+Example:
+
+```toml
+[rule.filename-format]
+    arguments=["^[_a-z][_a-z0-9]*.go$"]
+```
+
 ## flag-parameter
 
 _Description_: If a function controls the flow of another by passing it information on what to do, both functions are said to be [control-coupled](https://en.wikipedia.org/wiki/Coupling_(computer_programming)#Procedural_programming).
diff --git a/config/config.go b/config/config.go
index 5da64b6..16559f5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -97,6 +97,7 @@ var allRules = append([]lint.Rule{
 	&rule.MaxControlNestingRule{},
 	&rule.CommentsDensityRule{},
 	&rule.FileLengthLimitRule{},
+	&rule.FilenameFormatRule{},
 }, defaultRules...)
 
 var allFormatters = []lint.Formatter{
diff --git a/lint/package.go b/lint/package.go
index 441e0f4..2ab035f 100644
--- a/lint/package.go
+++ b/lint/package.go
@@ -166,17 +166,17 @@ func (p *Package) scanSortable() {
 
 	// bitfield for which methods exist on each type.
 	const (
-		Len = 1 << iota
-		Less
-		Swap
+		bfLen = 1 << iota
+		bfLess
+		bfSwap
 	)
-	nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap}
+	nmap := map[string]int{"Len": bfLen, "Less": bfLess, "Swap": bfSwap}
 	has := make(map[string]int)
 	for _, f := range p.files {
 		ast.Walk(&walker{nmap, has}, f.AST)
 	}
 	for typ, ms := range has {
-		if ms == Len|Less|Swap {
+		if ms == bfLen|bfLess|bfSwap {
 			p.sortable[typ] = true
 		}
 	}
diff --git a/revive.toml b/revive.toml
new file mode 100644
index 0000000..3d47522
--- /dev/null
+++ b/revive.toml
@@ -0,0 +1,38 @@
+# This configuration for the revive linter used for linting the revive's codebase itself.
+# See .github/workflows/lint.yaml.
+
+ignoreGeneratedHeader = false
+severity = "warning"
+confidence = 0.8
+errorCode = 1
+warningCode = 1
+
+[rule.bare-return]
+[rule.blank-imports]
+[rule.context-as-argument]
+[rule.context-keys-type]
+[rule.dot-imports]
+[rule.empty-block]
+[rule.empty-lines]
+[rule.error-naming]
+[rule.error-return]
+[rule.error-strings]
+[rule.errorf]
+[rule.exported]
+[rule.increment-decrement]
+[rule.indent-error-flow]
+[rule.line-length-limit]
+    arguments = [200]
+[rule.package-comments]
+[rule.range]
+[rule.receiver-naming]
+[rule.redefines-builtin-id]
+[rule.superfluous-else]
+[rule.time-naming]
+[rule.unexported-naming]
+[rule.unexported-return]
+[rule.unreachable-code]
+[rule.unused-parameter]
+[rule.useless-break]
+[rule.var-declaration]
+[rule.var-naming]
diff --git a/rule/add-constant.go b/rule/add-constant.go
index f7a2447..233f1d8 100644
--- a/rule/add-constant.go
+++ b/rule/add-constant.go
@@ -160,14 +160,14 @@ func (w *lintAddConstantRule) isIgnoredFunc(fName string) bool {
 }
 
 func (w *lintAddConstantRule) checkStrLit(n *ast.BasicLit) {
-	const IgnoreMarker = -1
+	const ignoreMarker = -1
 
 	if w.allowList[kindSTRING][n.Value] {
 		return
 	}
 
 	count := w.strLits[n.Value]
-	mustCheck := count > IgnoreMarker
+	mustCheck := count > ignoreMarker
 	if mustCheck {
 		w.strLits[n.Value] = count + 1
 		if w.strLits[n.Value] > w.strLitLimit {
diff --git a/rule/comments-density.go b/rule/comments-density.go
index 5956fea..c5298ea 100644
--- a/rule/comments-density.go
+++ b/rule/comments-density.go
@@ -53,7 +53,8 @@ func (r *CommentsDensityRule) Apply(file *lint.File, arguments lint.Arguments) [
 			{
 				Node:       file.AST,
 				Confidence: 1,
-				Failure:    fmt.Sprintf("the file has a comment density of %2.f%% (%d comment lines for %d code lines) but expected a minimum of %d%%", density, commentsLines, statementsCount, r.minimumCommentsDensity),
+				Failure: fmt.Sprintf("the file has a comment density of %2.f%% (%d comment lines for %d code lines) but expected a minimum of %d%%",
+					density, commentsLines, statementsCount, r.minimumCommentsDensity),
 			},
 		}
 	}
diff --git a/rule/dot-imports.go b/rule/dot-imports.go
index 523b481..df0b2a7 100644
--- a/rule/dot-imports.go
+++ b/rule/dot-imports.go
@@ -59,17 +59,17 @@ func (r *DotImportsRule) configure(arguments lint.Arguments) {
 	}
 
 	if allowedPkgArg, ok := args["allowedPackages"]; ok {
-		if pkgs, ok := allowedPkgArg.([]any); ok {
-			for _, p := range pkgs {
-				if pkg, ok := p.(string); ok {
-					r.allowedPackages.add(pkg)
-				} else {
-					panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p))
-				}
-			}
-		} else {
+		pkgs, ok := allowedPkgArg.([]any)
+		if !ok {
 			panic(fmt.Sprintf("Invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg))
 		}
+		for _, p := range pkgs {
+			pkg, ok := p.(string)
+			if !ok {
+				panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p))
+			}
+			r.allowedPackages.add(pkg)
+		}
 	}
 }
 
diff --git a/rule/early-return.go b/rule/early-return.go
index 9c04a1d..62d491f 100644
--- a/rule/early-return.go
+++ b/rule/early-return.go
@@ -21,27 +21,27 @@ func (*EarlyReturnRule) Name() string {
 	return "early-return"
 }
 
-// CheckIfElse evaluates the rule against an ifelse.Chain.
-func (*EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
+// CheckIfElse evaluates the rule against an ifelse.Chain and returns a failure message if applicable.
+func (*EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) string {
 	if !chain.Else.Deviates() {
 		// this rule only applies if the else-block deviates control flow
-		return
+		return ""
 	}
 
 	if chain.HasPriorNonDeviating && !chain.If.IsEmpty() {
 		// if we de-indent this block then a previous branch
 		// might flow into it, affecting program behaviour
-		return
+		return ""
 	}
 
 	if chain.If.Deviates() {
 		// avoid overlapping with superfluous-else
-		return
+		return ""
 	}
 
 	if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.If.HasDecls) {
 		// avoid increasing variable scope
-		return
+		return ""
 	}
 
 	if chain.If.IsEmpty() {
diff --git a/rule/filename-format.go b/rule/filename-format.go
new file mode 100644
index 0000000..49fdf9c
--- /dev/null
+++ b/rule/filename-format.go
@@ -0,0 +1,87 @@
+package rule
+
+import (
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"sync"
+	"unicode"
+
+	"github.com/mgechev/revive/lint"
+)
+
+// FilenameFormatRule lints source filenames according to a set of regular expressions given as arguments
+type FilenameFormatRule struct {
+	format *regexp.Regexp
+	sync.Mutex
+}
+
+// Apply applies the rule to the given file.
+func (r *FilenameFormatRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
+	r.configure(arguments)
+
+	filename := filepath.Base(file.Name)
+	if r.format.MatchString(filename) {
+		return nil
+	}
+
+	failureMsg := fmt.Sprintf("Filename %s is not of the format %s.%s", filename, r.format.String(), r.getMsgForNonASCIIChars(filename))
+	return []lint.Failure{{
+		Confidence: 1,
+		Failure:    failureMsg,
+		RuleName:   r.Name(),
+		Node:       file.AST.Name,
+	}}
+}
+
+func (r *FilenameFormatRule) getMsgForNonASCIIChars(str string) string {
+	result := ""
+	for _, c := range str {
+		if c <= unicode.MaxASCII {
+			continue
+		}
+
+		result += fmt.Sprintf(" Non ASCII character %c (%U) found.", c, c)
+	}
+
+	return result
+}
+
+// Name returns the rule name.
+func (*FilenameFormatRule) Name() string {
+	return "filename-format"
+}
+
+var defaultFormat = regexp.MustCompile("^[_A-Za-z0-9][_A-Za-z0-9-]*.go$")
+
+func (r *FilenameFormatRule) configure(arguments lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
+
+	if r.format != nil {
+		return
+	}
+
+	argsCount := len(arguments)
+	if argsCount == 0 {
+		r.format = defaultFormat
+		return
+	}
+
+	if argsCount > 1 {
+		panic(fmt.Sprintf("rule %q expects only one argument, got %d %v", r.Name(), argsCount, arguments))
+	}
+
+	arg := arguments[0]
+	str, ok := arg.(string)
+	if !ok {
+		panic(fmt.Sprintf("rule %q expects a string argument, got %v of type %T", r.Name(), arg, arg))
+	}
+
+	format, err := regexp.Compile(str)
+	if err != nil {
+		panic(fmt.Sprintf("rule %q expects a valid regexp argument, got %v for %s", r.Name(), err, arg))
+	}
+
+	r.format = format
+}
diff --git a/rule/indent-error-flow.go b/rule/indent-error-flow.go
index 294ceef..ebc1e79 100644
--- a/rule/indent-error-flow.go
+++ b/rule/indent-error-flow.go
@@ -18,27 +18,27 @@ func (*IndentErrorFlowRule) Name() string {
 	return "indent-error-flow"
 }
 
-// CheckIfElse evaluates the rule against an ifelse.Chain.
-func (*IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
+// CheckIfElse evaluates the rule against an ifelse.Chain and returns a failure message if applicable.
+func (*IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) string {
 	if !chain.If.Deviates() {
 		// this rule only applies if the if-block deviates control flow
-		return
+		return ""
 	}
 
 	if chain.HasPriorNonDeviating {
 		// if we de-indent the "else" block then a previous branch
 		// might flow into it, affecting program behaviour
-		return
+		return ""
 	}
 
 	if !chain.If.Returns() {
 		// avoid overlapping with superfluous-else
-		return
+		return ""
 	}
 
 	if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) {
 		// avoid increasing variable scope
-		return
+		return ""
 	}
 
 	return "if block ends with a return statement, so drop this else and outdent its block"
diff --git a/rule/string-format.go b/rule/string-format.go
index 62283cb..ea19eab 100644
--- a/rule/string-format.go
+++ b/rule/string-format.go
@@ -216,7 +216,7 @@ func (w lintStringFormatRule) Visit(node ast.Node) ast.Visitor {
 	for _, rule := range w.rules {
 		for _, scope := range rule.scopes {
 			if scope.funcName == callName {
-				rule.Apply(call)
+				rule.apply(call, scope)
 			}
 		}
 	}
@@ -251,81 +251,73 @@ func (lintStringFormatRule) getCallName(call *ast.CallExpr) (callName string, ok
 
 // #region Linting logic
 
-// Apply a single format rule to a call expression (should be done after verifying the that the call expression matches the rule's scope)
-func (r *stringFormatSubrule) Apply(call *ast.CallExpr) {
-	for _, scope := range r.scopes {
-		if len(call.Args) <= scope.argument {
+// apply a single format rule to a call expression (should be done after verifying the that the call expression matches the rule's scope)
+func (r *stringFormatSubrule) apply(call *ast.CallExpr, scope *stringFormatSubruleScope) {
+	if len(call.Args) <= scope.argument {
+		return
+	}
+
+	arg := call.Args[scope.argument]
+	var lit *ast.BasicLit
+	if len(scope.field) > 0 {
+		// Try finding the scope's Field, treating arg as a composite literal
+		composite, ok := arg.(*ast.CompositeLit)
+		if !ok {
 			return
 		}
-
-		arg := call.Args[scope.argument]
-		var lit *ast.BasicLit
-		if len(scope.field) > 0 {
-			// Try finding the scope's Field, treating arg as a composite literal
-			composite, ok := arg.(*ast.CompositeLit)
+		for _, el := range composite.Elts {
+			kv, ok := el.(*ast.KeyValueExpr)
 			if !ok {
-				return
+				continue
 			}
-			for _, el := range composite.Elts {
-				kv, ok := el.(*ast.KeyValueExpr)
-				if !ok {
-					continue
-				}
-				key, ok := kv.Key.(*ast.Ident)
-				if !ok || key.Name != scope.field {
-					continue
-				}
-
-				// We're now dealing with the exact field in the rule's scope, so if anything fails, we can safely return instead of continuing the loop
-				lit, ok = kv.Value.(*ast.BasicLit)
-				if !ok || lit.Kind != token.STRING {
-					return
-				}
+			key, ok := kv.Key.(*ast.Ident)
+			if !ok || key.Name != scope.field {
+				continue
 			}
-		} else {
-			var ok bool
-			// Treat arg as a string literal
-			lit, ok = arg.(*ast.BasicLit)
+
+			// We're now dealing with the exact field in the rule's scope, so if anything fails, we can safely return instead of continuing the loop
+			lit, ok = kv.Value.(*ast.BasicLit)
 			if !ok || lit.Kind != token.STRING {
 				return
 			}
 		}
-		// Unquote the string literal before linting
-		unquoted := lit.Value[1 : len(lit.Value)-1]
-		r.lintMessage(unquoted, lit)
-	}
-}
-
-func (r *stringFormatSubrule) lintMessage(s string, node ast.Node) {
-	if r.negated {
-		if !r.regexp.MatchString(s) {
+	} else {
+		var ok bool
+		// Treat arg as a string literal
+		lit, ok = arg.(*ast.BasicLit)
+		if !ok || lit.Kind != token.STRING {
 			return
 		}
-		// Fail if the string does match the user's regex
-		var failure string
-		if len(r.errorMessage) > 0 {
-			failure = r.errorMessage
-		} else {
-			failure = fmt.Sprintf("string literal matches user defined regex /%s/", r.regexp.String())
-		}
-		r.parent.onFailure(lint.Failure{
-			Confidence: 1,
-			Failure:    failure,
-			Node:       node,
-		})
+	}
+	// Unquote the string literal before linting
+	unquoted := lit.Value[1 : len(lit.Value)-1]
+	if r.stringIsOK(unquoted) {
 		return
 	}
 
-	// Fail if the string does NOT match the user's regex
-	if r.regexp.MatchString(s) {
-		return
+	r.generateFailure(unquoted, lit)
+}
+
+func (r *stringFormatSubrule) stringIsOK(s string) bool {
+	matches := r.regexp.MatchString(s)
+	if r.negated {
+		return !matches
 	}
+
+	return matches
+}
+
+func (r *stringFormatSubrule) generateFailure(s string, node ast.Node) {
 	var failure string
-	if len(r.errorMessage) > 0 {
+	switch {
+	case len(r.errorMessage) > 0:
 		failure = r.errorMessage
-	} else {
+	case r.negated:
+		failure = fmt.Sprintf("string literal matches user defined regex /%s/", r.regexp.String())
+	case !r.negated:
 		failure = fmt.Sprintf("string literal doesn't match user defined regex /%s/", r.regexp.String())
 	}
+
 	r.parent.onFailure(lint.Failure{
 		Confidence: 1,
 		Failure:    failure,
diff --git a/rule/superfluous-else.go b/rule/superfluous-else.go
index 2aa1b6b..18e8f3b 100644
--- a/rule/superfluous-else.go
+++ b/rule/superfluous-else.go
@@ -2,6 +2,7 @@ package rule
 
 import (
 	"fmt"
+
 	"github.com/mgechev/revive/internal/ifelse"
 	"github.com/mgechev/revive/lint"
 )
@@ -19,27 +20,27 @@ func (*SuperfluousElseRule) Name() string {
 	return "superfluous-else"
 }
 
-// CheckIfElse evaluates the rule against an ifelse.Chain.
-func (*SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
+// CheckIfElse evaluates the rule against an ifelse.Chain and returns a failure message if applicable.
+func (*SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) string {
 	if !chain.If.Deviates() {
 		// this rule only applies if the if-block deviates control flow
-		return
+		return ""
 	}
 
 	if chain.HasPriorNonDeviating {
 		// if we de-indent the "else" block then a previous branch
 		// might flow into it, affecting program behaviour
-		return
+		return ""
 	}
 
 	if chain.If.Returns() {
 		// avoid overlapping with indent-error-flow
-		return
+		return ""
 	}
 
 	if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) {
 		// avoid increasing variable scope
-		return
+		return ""
 	}
 
 	return fmt.Sprintf("if block ends with %v, so drop this else and outdent its block", chain.If.LongString())
diff --git a/rule/use-any.go b/rule/use-any.go
index bdf3c93..88160c2 100644
--- a/rule/use-any.go
+++ b/rule/use-any.go
@@ -47,7 +47,7 @@ func (w lintUseAny) Visit(n ast.Node) ast.Visitor {
 		Node:       n,
 		Confidence: 1,
 		Category:   "naming",
-		Failure:    "since GO 1.18 'interface{}' can be replaced by 'any'",
+		Failure:    "since Go 1.18 'interface{}' can be replaced by 'any'",
 	})
 
 	return w
diff --git a/rule/var-naming.go b/rule/var-naming.go
index 9d9c739..5a4d0dc 100644
--- a/rule/var-naming.go
+++ b/rule/var-naming.go
@@ -264,17 +264,17 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor {
 }
 
 func getList(arg any, argName string) []string {
-	temp, ok := arg.([]any)
+	args, ok := arg.([]any)
 	if !ok {
 		panic(fmt.Sprintf("Invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg))
 	}
 	var list []string
-	for _, v := range temp {
-		if val, ok := v.(string); ok {
-			list = append(list, val)
-		} else {
+	for _, v := range args {
+		val, ok := v.(string)
+		if !ok {
 			panic(fmt.Sprintf("Invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg))
 		}
+		list = append(list, val)
 	}
 	return list
 }
diff --git "a/testdata/filenam\320\265-with-non-ascii-char.go" "b/testdata/filenam\320\265-with-non-ascii-char.go"
new file mode 100644
index 0000000..a4c665e
--- /dev/null
+++ "b/testdata/filenam\320\265-with-non-ascii-char.go"
@@ -0,0 +1,3 @@
+package main
+
+// MATCH:1 /Filename filenamе-with-non-ascii-char.go is not of the format ^[_A-Za-z0-9][_A-Za-z0-9-]*.go$. Non ASCII character е (U+0435) found./


Test patch:
diff --git a/test/file-filter_test.go b/test/file-filter_test.go
index 7729a71..a73d311 100644
--- a/test/file-filter_test.go
+++ b/test/file-filter_test.go
@@ -7,14 +7,14 @@ import (
 )
 
 type TestFileFilterRule struct {
-	WasApplyed bool
+	WasApplied bool
 }
 
 var _ lint.Rule = (*TestFileFilterRule)(nil)
 
 func (*TestFileFilterRule) Name() string { return "test-file-filter" }
 func (tfr *TestFileFilterRule) Apply(*lint.File, lint.Arguments) []lint.Failure {
-	tfr.WasApplyed = true
+	tfr.WasApplied = true
 	return nil
 }
 
@@ -22,7 +22,7 @@ func TestFileExcludeFilterAtRuleLevel(t *testing.T) {
 	t.Run("is called if no excludes", func(t *testing.T) {
 		rule := &TestFileFilterRule{}
 		testRule(t, "file-to-exclude", rule, &lint.RuleConfig{})
-		if !rule.WasApplyed {
+		if !rule.WasApplied {
 			t.Fatal("should call rule if no excludes")
 		}
 	})
@@ -31,7 +31,7 @@ func TestFileExcludeFilterAtRuleLevel(t *testing.T) {
 		cfg := &lint.RuleConfig{Exclude: []string{"no-matched.go"}}
 		cfg.Initialize()
 		testRule(t, "file-to-exclude", rule, cfg)
-		if !rule.WasApplyed {
+		if !rule.WasApplied {
 			t.Fatal("should call rule if no excludes")
 		}
 	})
@@ -41,7 +41,7 @@ func TestFileExcludeFilterAtRuleLevel(t *testing.T) {
 		cfg := &lint.RuleConfig{Exclude: []string{"../testdata/file-to-exclude.go"}}
 		cfg.Initialize()
 		testRule(t, "file-to-exclude", rule, cfg)
-		if rule.WasApplyed {
+		if rule.WasApplied {
 			t.Fatal("should not call rule if excluded")
 		}
 	})
diff --git a/test/filename-format_test.go b/test/filename-format_test.go
new file mode 100644
index 0000000..2f0ef76
--- /dev/null
+++ b/test/filename-format_test.go
@@ -0,0 +1,16 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/rule"
+)
+
+func TestLintFilenameFormat(t *testing.T) {
+	testRule(t, "filename-ok-default", &rule.FilenameFormatRule{}, &lint.RuleConfig{})
+
+	testRule(t, "filenamе-with-non-ascii-char", &rule.FilenameFormatRule{}, &lint.RuleConfig{})
+
+	testRule(t, "filename_with_underscores", &rule.FilenameFormatRule{}, &lint.RuleConfig{Arguments: []any{"^[A-Za-z][A-Za-z0-9]*.go$"}})
+}
diff --git a/test/string-format_test.go b/test/string-format_test.go
index 5135ff0..2ca1bd1 100644
--- a/test/string-format_test.go
+++ b/test/string-format_test.go
@@ -171,3 +171,13 @@ func TestStringFormatArgumentParsing(t *testing.T) {
 		})
 	}
 }
+
+func TestStringFormatDuplicatedStrings(t *testing.T) {
+	testRule(t, "string-format-issue-1063", &rule.StringFormatRule{}, &lint.RuleConfig{
+		Arguments: lint.Arguments{[]any{
+			"fmt.Errorf[0],errors.New[0]",
+			"/^([^A-Z]|$)/",
+			"must not start with a capital letter",
+		}},
+	})
+}
diff --git a/test/utils_test.go b/test/utils_test.go
index 89018e0..7cc5f25 100644
--- a/test/utils_test.go
+++ b/test/utils_test.go
@@ -173,7 +173,6 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
 					t.Fatalf("At %v:%d: %v", filename, ln, err)
 				}
 				ins = append(ins, jsonInst)
-				break
 			case "classic":
 				match, err := extractPattern(line)
 				if err != nil {
@@ -198,9 +197,7 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
 					Match:       match,
 					Replacement: repl,
 				})
-				break
 			}
-
 		}
 	}
 	return ins
@@ -210,7 +207,7 @@ func extractInstructionFromJSON(line string, lineNumber int) (instruction, error
 	// Use the json.Unmarshal function to parse the JSON into the struct
 	var jsonInst JSONInstruction
 	if err := json.Unmarshal([]byte(line), &jsonInst); err != nil {
-		fmt.Println("Error parsing JSON:", err)
+		return instruction{}, fmt.Errorf("parsing json instruction: %w", err)
 	}
 
 	ins := instruction{
diff --git a/testdata/filename-ok-default.go b/testdata/filename-ok-default.go
new file mode 100644
index 0000000..06ab7d0
--- /dev/null
+++ b/testdata/filename-ok-default.go
@@ -0,0 +1,1 @@
+package main
diff --git a/testdata/filename_with_underscores.go b/testdata/filename_with_underscores.go
new file mode 100644
index 0000000..cbdc6b5
--- /dev/null
+++ b/testdata/filename_with_underscores.go
@@ -0,0 +1,3 @@
+package main
+
+// MATCH:1 /Filename filename_with_underscores.go is not of the format ^[A-Za-z][A-Za-z0-9]*.go$./
diff --git a/testdata/string-format-issue-1063.go b/testdata/string-format-issue-1063.go
new file mode 100644
index 0000000..1cb23c9
--- /dev/null
+++ b/testdata/string-format-issue-1063.go
@@ -0,0 +1,9 @@
+package fixtures
+
+import (
+	"errors"
+)
+
+func ReturnError() error {
+	return errors.New("This is an error.") // MATCH /must not start with a capital letter/
+}
diff --git a/testdata/use-any.go b/testdata/use-any.go
index bc6e647..9891e84 100644
--- a/testdata/use-any.go
+++ b/testdata/use-any.go
@@ -1,19 +1,19 @@
 package pkg
 
-var i interface{} // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
+var i interface{} // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
 
-type t interface{}   // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-type a = interface{} // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
+type t interface{}   // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+type a = interface{} // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
 
-func any1(a interface{}) { // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-	m1 := map[interface{}]string{}     // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-	m2 := map[int]interface{}{}        // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-	a := []interface{}{}               // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-	m3 := make(map[int]interface{}, 1) // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
-	a2 := make([]interface{}, 2)       // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
+func any1(a interface{}) { // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+	m1 := map[interface{}]string{}     // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+	m2 := map[int]interface{}{}        // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+	a := []interface{}{}               // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+	m3 := make(map[int]interface{}, 1) // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
+	a2 := make([]interface{}, 2)       // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
 }
 
-func any2(a int) interface{} {} // MATCH /since GO 1.18 'interface{}' can be replaced by 'any'/
+func any2(a int) interface{} {} // MATCH /since Go 1.18 'interface{}' can be replaced by 'any'/
 
 var ni interface{ Close() }