Skip to content

Commit

Permalink
adds filename-format rule (#1092)
Browse files Browse the repository at this point in the history
  • Loading branch information
chavacava authored Nov 2, 2024
1 parent 511e4e6 commit eb18252
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions RULES_DESCRIPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ var allRules = append([]lint.Rule{
&rule.MaxControlNestingRule{},
&rule.CommentsDensityRule{},
&rule.FileLengthLimitRule{},
&rule.FilenameFormatRule{},
}, defaultRules...)

var allFormatters = []lint.Formatter{
Expand Down
87 changes: 87 additions & 0 deletions rule/filename-format.go
Original file line number Diff line number Diff line change
@@ -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
}
16 changes: 16 additions & 0 deletions test/filename-format_test.go
Original file line number Diff line number Diff line change
@@ -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$"}})
}
1 change: 1 addition & 0 deletions testdata/filename-ok-default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
3 changes: 3 additions & 0 deletions testdata/filename_with_underscores.go
Original file line number Diff line number Diff line change
@@ -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$./
3 changes: 3 additions & 0 deletions testdata/filenamе-with-non-ascii-char.go
Original file line number Diff line number Diff line change
@@ -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./

0 comments on commit eb18252

Please sign in to comment.