github.com/ftvbftvbq/golangci-lint@v1.10.1/pkg/packages/resolver.go (about)

     1  package packages
     2  
     3  import (
     4  	"fmt"
     5  	"go/build"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/golangci/golangci-lint/pkg/fsutils"
    14  	"github.com/golangci/golangci-lint/pkg/logutils"
    15  )
    16  
    17  type Resolver struct {
    18  	excludeDirs map[string]*regexp.Regexp
    19  	buildTags   []string
    20  
    21  	skippedDirs []string
    22  	log         logutils.Log
    23  
    24  	wd                  string // working directory
    25  	importErrorsOccured int    // count of errors because too bad files in packages
    26  }
    27  
    28  func NewResolver(buildTags, excludeDirs []string, log logutils.Log) (*Resolver, error) {
    29  	excludeDirsMap := map[string]*regexp.Regexp{}
    30  	for _, dir := range excludeDirs {
    31  		re, err := regexp.Compile(dir)
    32  		if err != nil {
    33  			return nil, fmt.Errorf("can't compile regexp %q: %s", dir, err)
    34  		}
    35  
    36  		excludeDirsMap[dir] = re
    37  	}
    38  
    39  	wd, err := fsutils.Getwd()
    40  	if err != nil {
    41  		return nil, fmt.Errorf("can't get working dir: %s", err)
    42  	}
    43  
    44  	return &Resolver{
    45  		excludeDirs: excludeDirsMap,
    46  		buildTags:   buildTags,
    47  		log:         log,
    48  		wd:          wd,
    49  	}, nil
    50  }
    51  
    52  func (r Resolver) isIgnoredDir(dir string) bool {
    53  	cleanName := filepath.Clean(dir)
    54  
    55  	dirName := filepath.Base(cleanName)
    56  
    57  	// https://github.com/golang/dep/issues/298
    58  	// https://github.com/tools/godep/issues/140
    59  	if strings.HasPrefix(dirName, ".") && dirName != "." && dirName != ".." {
    60  		return true
    61  	}
    62  	if strings.HasPrefix(dirName, "_") {
    63  		return true
    64  	}
    65  
    66  	for _, dirExludeRe := range r.excludeDirs {
    67  		if dirExludeRe.MatchString(cleanName) {
    68  			return true
    69  		}
    70  	}
    71  
    72  	return false
    73  }
    74  
    75  func (r *Resolver) resolveRecursively(root string, prog *Program) error {
    76  	// import root
    77  	if err := r.resolveDir(root, prog); err != nil {
    78  		return err
    79  	}
    80  
    81  	fis, err := ioutil.ReadDir(root)
    82  	if err != nil {
    83  		return fmt.Errorf("can't read dir %s: %s", root, err)
    84  	}
    85  	// TODO: pass cached fis to build.Context
    86  
    87  	for _, fi := range fis {
    88  		if !fi.IsDir() {
    89  			// ignore files: they were already imported by resolveDir(root)
    90  			continue
    91  		}
    92  
    93  		subdir := filepath.Join(root, fi.Name())
    94  
    95  		// Normalize each subdir because working directory can be one of these subdirs:
    96  		// working dir = /app/subdir, resolve root is ../, without this normalization
    97  		// path of subdir will be "../subdir" but it must be ".".
    98  		// Normalize path before checking is ignored dir.
    99  		subdir, err := r.normalizePath(subdir)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		if r.isIgnoredDir(subdir) {
   105  			r.skippedDirs = append(r.skippedDirs, subdir)
   106  			continue
   107  		}
   108  
   109  		if err := r.resolveRecursively(subdir, prog); err != nil {
   110  			return err
   111  		}
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (r *Resolver) resolveDir(dir string, prog *Program) error {
   118  	// TODO: fork build.Import to reuse AST parsing
   119  	bp, err := prog.bctx.ImportDir(dir, build.ImportComment|build.IgnoreVendor)
   120  	if err != nil {
   121  		if _, nogo := err.(*build.NoGoError); nogo {
   122  			// Don't complain if the failure is due to no Go source files.
   123  			return nil
   124  		}
   125  
   126  		err = fmt.Errorf("can't import dir %q: %s", dir, err)
   127  		r.importErrorsOccured++
   128  		if r.importErrorsOccured >= 10 {
   129  			return err
   130  		}
   131  
   132  		r.log.Warnf("Can't analyze dir %q: %s", dir, err)
   133  		return nil
   134  	}
   135  
   136  	pkg := Package{
   137  		bp: bp,
   138  	}
   139  	prog.addPackage(&pkg)
   140  	return nil
   141  }
   142  
   143  func (r Resolver) addFakePackage(filePath string, prog *Program) {
   144  	// Don't take build tags, is it test file or not, etc
   145  	// into account. If user explicitly wants to analyze this file
   146  	// do it.
   147  	p := Package{
   148  		bp: &build.Package{
   149  			// TODO: detect is it test file or not: without that we can't analyze only one test file
   150  			GoFiles: []string{filePath},
   151  		},
   152  		isFake: true,
   153  		dir:    filepath.Dir(filePath),
   154  	}
   155  	prog.addPackage(&p)
   156  }
   157  
   158  func (r Resolver) Resolve(paths ...string) (prog *Program, err error) {
   159  	startedAt := time.Now()
   160  	defer func() {
   161  		r.log.Infof("Paths resolving took %s: %s", time.Since(startedAt), prog)
   162  	}()
   163  
   164  	if len(paths) == 0 {
   165  		return nil, fmt.Errorf("no paths are set")
   166  	}
   167  
   168  	bctx := build.Default
   169  	bctx.BuildTags = append(bctx.BuildTags, r.buildTags...)
   170  	prog = &Program{
   171  		bctx: bctx,
   172  	}
   173  
   174  	for _, path := range paths {
   175  		if err := r.resolvePath(path, prog); err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  
   180  	if len(r.skippedDirs) != 0 {
   181  		r.log.Infof("Skipped dirs: %s", r.skippedDirs)
   182  	}
   183  
   184  	return prog, nil
   185  }
   186  
   187  func (r *Resolver) normalizePath(path string) (string, error) {
   188  	return fsutils.ShortestRelPath(path, r.wd)
   189  }
   190  
   191  func (r *Resolver) resolvePath(path string, prog *Program) error {
   192  	needRecursive := strings.HasSuffix(path, "/...")
   193  	if needRecursive {
   194  		path = filepath.Dir(path)
   195  	}
   196  
   197  	evalPath, err := filepath.EvalSymlinks(path)
   198  	if err != nil {
   199  		return fmt.Errorf("can't eval symlinks for path %s: %s", path, err)
   200  	}
   201  	path = evalPath
   202  
   203  	path, err = r.normalizePath(path)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	if needRecursive {
   209  		if err = r.resolveRecursively(path, prog); err != nil {
   210  			return fmt.Errorf("can't recursively resolve %s: %s", path, err)
   211  		}
   212  
   213  		return nil
   214  	}
   215  
   216  	fi, err := os.Stat(path)
   217  	if err != nil {
   218  		return fmt.Errorf("can't find path %s: %s", path, err)
   219  	}
   220  
   221  	if fi.IsDir() {
   222  		if err := r.resolveDir(path, prog); err != nil {
   223  			return fmt.Errorf("can't resolve dir %s: %s", path, err)
   224  		}
   225  		return nil
   226  	}
   227  
   228  	r.addFakePackage(path, prog)
   229  	return nil
   230  }