github.com/alecthomas/golangci-lint@v1.4.2-0.20180609094924-581a3564ff68/pkg/fsutils/path_resolver.go (about) 1 package fsutils 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "sort" 9 "strings" 10 ) 11 12 type PathResolver struct { 13 excludeDirs map[string]*regexp.Regexp 14 allowedFileExtensions map[string]bool 15 includeTests bool 16 } 17 18 type pathResolveState struct { 19 files map[string]bool 20 dirs map[string]bool 21 } 22 23 func (s *pathResolveState) addFile(path string) { 24 s.files[filepath.Clean(path)] = true 25 } 26 27 func (s *pathResolveState) addDir(path string) { 28 s.dirs[filepath.Clean(path)] = true 29 } 30 31 type PathResolveResult struct { 32 files []string 33 dirs []string 34 } 35 36 func (prr PathResolveResult) Files() []string { 37 return prr.files 38 } 39 40 func (prr PathResolveResult) Dirs() []string { 41 return prr.dirs 42 } 43 44 func (s pathResolveState) toResult() *PathResolveResult { 45 res := &PathResolveResult{ 46 files: []string{}, 47 dirs: []string{}, 48 } 49 for f := range s.files { 50 res.files = append(res.files, f) 51 } 52 for d := range s.dirs { 53 res.dirs = append(res.dirs, d) 54 } 55 56 sort.Strings(res.files) 57 sort.Strings(res.dirs) 58 return res 59 } 60 61 func NewPathResolver(excludeDirs, allowedFileExtensions []string, includeTests bool) (*PathResolver, error) { 62 excludeDirsMap := map[string]*regexp.Regexp{} 63 for _, dir := range excludeDirs { 64 re, err := regexp.Compile(dir) 65 if err != nil { 66 return nil, fmt.Errorf("can't compile regexp %q: %s", dir, err) 67 } 68 69 excludeDirsMap[dir] = re 70 } 71 72 allowedFileExtensionsMap := map[string]bool{} 73 for _, fe := range allowedFileExtensions { 74 allowedFileExtensionsMap[fe] = true 75 } 76 77 return &PathResolver{ 78 excludeDirs: excludeDirsMap, 79 allowedFileExtensions: allowedFileExtensionsMap, 80 includeTests: includeTests, 81 }, nil 82 } 83 84 func (pr PathResolver) isIgnoredDir(dir string) bool { 85 dirName := filepath.Base(filepath.Clean(dir)) // ignore dirs on any depth level 86 87 // https://github.com/golang/dep/issues/298 88 // https://github.com/tools/godep/issues/140 89 if strings.HasPrefix(dirName, ".") && dirName != "." && dirName != ".." { 90 return true 91 } 92 if strings.HasPrefix(dirName, "_") { 93 return true 94 } 95 96 for _, dirExludeRe := range pr.excludeDirs { 97 if dirExludeRe.MatchString(dirName) { 98 return true 99 } 100 } 101 102 return false 103 } 104 105 func (pr PathResolver) isAllowedFile(path string) bool { 106 if !pr.includeTests && strings.HasSuffix(path, "_test.go") { 107 return false 108 } 109 110 return pr.allowedFileExtensions[filepath.Ext(path)] 111 } 112 113 func (pr PathResolver) resolveRecursively(root string, state *pathResolveState) error { 114 walkErr := filepath.Walk(root, func(p string, i os.FileInfo, err error) error { 115 if err != nil { 116 return err 117 } 118 119 if i.IsDir() { 120 if pr.isIgnoredDir(p) { 121 return filepath.SkipDir 122 } 123 state.addDir(p) 124 return nil 125 } 126 127 if pr.isAllowedFile(p) { 128 state.addFile(p) 129 } 130 return nil 131 }) 132 133 if walkErr != nil { 134 return fmt.Errorf("can't walk dir %s: %s", root, walkErr) 135 } 136 137 return nil 138 } 139 140 func (pr PathResolver) resolveDir(root string, state *pathResolveState) error { 141 walkErr := filepath.Walk(root, func(p string, i os.FileInfo, err error) error { 142 if err != nil { 143 return err 144 } 145 146 if i.IsDir() { 147 if filepath.Clean(p) != filepath.Clean(root) { 148 return filepath.SkipDir 149 } 150 state.addDir(p) 151 return nil 152 } 153 154 if pr.isAllowedFile(p) { 155 state.addFile(p) 156 } 157 return nil 158 }) 159 160 if walkErr != nil { 161 return fmt.Errorf("can't walk dir %s: %s", root, walkErr) 162 } 163 164 return nil 165 } 166 167 func (pr PathResolver) Resolve(paths ...string) (*PathResolveResult, error) { 168 if len(paths) == 0 { 169 return nil, fmt.Errorf("no paths are set") 170 } 171 172 state := &pathResolveState{ 173 files: map[string]bool{}, 174 dirs: map[string]bool{}, 175 } 176 for _, path := range paths { 177 if strings.HasSuffix(path, "/...") { 178 if err := pr.resolveRecursively(filepath.Dir(path), state); err != nil { 179 return nil, fmt.Errorf("can't recursively resolve %s: %s", path, err) 180 } 181 continue 182 } 183 184 fi, err := os.Stat(path) 185 if err != nil { 186 return nil, fmt.Errorf("can't find path %s: %s", path, err) 187 } 188 189 if fi.IsDir() { 190 if err := pr.resolveDir(path, state); err != nil { 191 return nil, fmt.Errorf("can't resolve dir %s: %s", path, err) 192 } 193 continue 194 } 195 196 state.addFile(path) 197 } 198 199 state.excludeDirsWithoutGoFiles() 200 201 return state.toResult(), nil 202 } 203 204 func (s *pathResolveState) excludeDirsWithoutGoFiles() { 205 dirToFiles := map[string]bool{} 206 for f := range s.files { 207 dir := filepath.Dir(f) 208 dirToFiles[dir] = true 209 } 210 211 for dir := range s.dirs { 212 if !dirToFiles[dir] { // no go files in this dir 213 delete(s.dirs, dir) 214 } 215 } 216 }