forked from mgechev/revive
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilefilter.go
128 lines (120 loc) · 3.27 KB
/
filefilter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 filter as matching everything
matchesAll bool
// marks filter as matching nothing
matchesNothing bool
}
// ParseFileFilter - creates [FileFilter] for given raw filter
// if empty string, it matches nothing
// if `*`, or `~`, it matches everything
// while regexp could be invalid, it could return it's compilation error
func ParseFileFilter(rawFilter string) (*FileFilter, error) {
rawFilter = strings.TrimSpace(rawFilter)
result := new(FileFilter)
result.raw = rawFilter
result.matchesNothing = len(result.raw) == 0
result.matchesAll = result.raw == "*" || result.raw == "~"
if !result.matchesAll && !result.matchesNothing {
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
}
if ff.matchesNothing {
return false
}
name = strings.ReplaceAll(name, "\\", "/")
return ff.rx.MatchString(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
}