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  }