forked from mgechev/revive
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
per-rule file exclude filters (mgechev#850)
- Loading branch information
Showing
11 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ vendor | |
*.swp | ||
dist/ | ||
*.log | ||
.vscode/settings.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
ignoreGeneratedHeader = false | ||
severity = "warning" | ||
confidence = 0.8 | ||
errorCode = 0 | ||
warningCode = 0 | ||
|
||
enableAllRules = false | ||
|
||
[rule.r1] | ||
# no excludes | ||
|
||
[rule.r2] | ||
exclude=["some/file.go"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package lint | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// FileFilter - file filter to exclude some files for rule | ||
// supports whole | ||
// 1. file/dir names : pkg/mypkg/my.go, | ||
// 2. globs: **/*.pb.go, | ||
// 3. regexes (~ prefix) ~-tmp\.\d+\.go | ||
// 4. special test marker `TEST` - treats as `~_test\.go` | ||
type FileFilter struct { | ||
// raw definition of filter inside config | ||
raw string | ||
// don't care what was at start, will use regexes inside | ||
rx *regexp.Regexp | ||
// marks that it was empty rule that matches everything | ||
matchesAll bool | ||
} | ||
|
||
// ParseFileFilter - creates [FileFilter] for given raw filter | ||
// if empty string, or `*`, or `~` is used it means "always true" | ||
// while regexp could be invalid, it could return it's compilation error | ||
func ParseFileFilter(cfgFilter string) (*FileFilter, error) { | ||
cfgFilter = strings.TrimSpace(cfgFilter) | ||
result := new(FileFilter) | ||
result.raw = cfgFilter | ||
result.matchesAll = len(result.raw) == 0 || result.raw == "*" || result.raw == "~" | ||
if !result.matchesAll { | ||
if err := result.prepareRegexp(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
return result, nil | ||
} | ||
|
||
func (ff *FileFilter) String() string { return ff.raw } | ||
|
||
// MatchFileName - checks if file name matches filter | ||
func (ff *FileFilter) MatchFileName(name string) bool { | ||
if ff.matchesAll { | ||
return true | ||
} | ||
name = strings.ReplaceAll(name, "\\", "/") | ||
return ff.rx.MatchString(name) | ||
} | ||
|
||
// Match - checks if given [File] matches filter | ||
func (ff *FileFilter) Match(f *File) bool { | ||
return ff.MatchFileName(f.Name) | ||
} | ||
|
||
var fileFilterInvalidGlobRegexp = regexp.MustCompile(`[^/]\*\*[^/]`) | ||
var escapeRegexSymbols = ".+{}()[]^$" | ||
|
||
func (ff *FileFilter) prepareRegexp() error { | ||
var err error | ||
var src = ff.raw | ||
if src == "TEST" { | ||
src = "~_test\\.go" | ||
} | ||
if strings.HasPrefix(src, "~") { | ||
ff.rx, err = regexp.Compile(src[1:]) | ||
if err != nil { | ||
return fmt.Errorf("invalid file filter [%s], regexp compile error: [%v]", ff.raw, err) | ||
} | ||
return nil | ||
} | ||
/* globs */ | ||
if strings.Contains(src, "*") { | ||
if fileFilterInvalidGlobRegexp.MatchString(src) { | ||
return fmt.Errorf("invalid file filter [%s], invalid glob pattern", ff.raw) | ||
} | ||
var rxBuild strings.Builder | ||
rxBuild.WriteByte('^') | ||
wasStar := false | ||
justDirGlob := false | ||
for _, c := range src { | ||
if c == '*' { | ||
if wasStar { | ||
rxBuild.WriteString(`[\s\S]*`) | ||
wasStar = false | ||
justDirGlob = true | ||
continue | ||
} | ||
wasStar = true | ||
continue | ||
} | ||
if wasStar { | ||
rxBuild.WriteString("[^/]*") | ||
wasStar = false | ||
} | ||
if strings.ContainsRune(escapeRegexSymbols, c) { | ||
rxBuild.WriteByte('\\') | ||
} | ||
rxBuild.WriteRune(c) | ||
if c == '/' && justDirGlob { | ||
rxBuild.WriteRune('?') | ||
} | ||
justDirGlob = false | ||
} | ||
if wasStar { | ||
rxBuild.WriteString("[^/]*") | ||
} | ||
rxBuild.WriteByte('$') | ||
ff.rx, err = regexp.Compile(rxBuild.String()) | ||
if err != nil { | ||
return fmt.Errorf("invalid file filter [%s], regexp compile error after glob expand: [%v]", ff.raw, err) | ||
} | ||
return nil | ||
} | ||
|
||
// it's whole file mask, just escape dots and normilze separators | ||
fillRx := src | ||
fillRx = strings.ReplaceAll(fillRx, "\\", "/") | ||
fillRx = strings.ReplaceAll(fillRx, ".", `\.`) | ||
fillRx = "^" + fillRx + "$" | ||
ff.rx, err = regexp.Compile(fillRx) | ||
if err != nil { | ||
return fmt.Errorf("invalid file filter [%s], regexp compile full path: [%v]", ff.raw, err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package lint_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/mgechev/revive/lint" | ||
) | ||
|
||
func TestFileFilter(t *testing.T) { | ||
t.Run("whole file name", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("a/b/c.go") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.MatchFileName("a/b/c.go") { | ||
t.Fatal("should match a/b/c.go") | ||
} | ||
if ff.MatchFileName("a/b/d.go") { | ||
t.Fatal("should not match") | ||
} | ||
}) | ||
|
||
t.Run("regex", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("~b/[cd].go$") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.MatchFileName("a/b/c.go") { | ||
t.Fatal("should match a/b/c.go") | ||
} | ||
if !ff.MatchFileName("b/d.go") { | ||
t.Fatal("should match b/d.go") | ||
} | ||
if ff.MatchFileName("b/x.go") { | ||
t.Fatal("should not match b/x.go") | ||
} | ||
}) | ||
|
||
t.Run("TEST well-known", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("TEST") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.MatchFileName("a/b/c_test.go") { | ||
t.Fatal("should match a/b/c_test.go") | ||
} | ||
if ff.MatchFileName("a/b/c_test_no.go") { | ||
t.Fatal("should not match a/b/c_test_no.go") | ||
} | ||
}) | ||
|
||
t.Run("glob *", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("a/b/*.pb.go") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.MatchFileName("a/b/xxx.pb.go") { | ||
t.Fatal("should match a/b/xxx.pb.go") | ||
} | ||
if !ff.MatchFileName("a/b/yyy.pb.go") { | ||
t.Fatal("should match a/b/yyy.pb.go") | ||
} | ||
if ff.MatchFileName("a/b/xxx.nopb.go") { | ||
t.Fatal("should not match a/b/xxx.nopb.go") | ||
} | ||
}) | ||
|
||
t.Run("glob **", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("a/**/*.pb.go") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.MatchFileName("a/x/xxx.pb.go") { | ||
t.Fatal("should match a/x/xxx.pb.go") | ||
} | ||
if !ff.MatchFileName("a/xxx.pb.go") { | ||
t.Fatal("should match a/xxx.pb.go") | ||
} | ||
if !ff.MatchFileName("a/x/y/z/yyy.pb.go") { | ||
t.Fatal("should match a/x/y/z/yyy.pb.go") | ||
} | ||
if ff.MatchFileName("a/b/xxx.nopb.go") { | ||
t.Fatal("should not match a/b/xxx.nopb.go") | ||
} | ||
}) | ||
|
||
t.Run("just to cover Match", func(t *testing.T) { | ||
ff, err := lint.ParseFileFilter("a/b/c.go") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !ff.Match(&lint.File{Name: "a/b/c.go"}) { | ||
t.Fatal("should match") | ||
} | ||
if ff.Match(&lint.File{Name: "a/b/d.go"}) { | ||
t.Fatal("should not match") | ||
} | ||
}) | ||
|
||
} |
Oops, something went wrong.