github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/golang/lint/golint/import.go (about)

     1  package main
     2  
     3  /*
     4  
     5  This file holds a direct copy of the import path matching code of
     6  https://yougam/libraries/golang/go/blob/master/src/cmd/go/main.go. It can be
     7  replaced when https://yougam/libraries/issue/8768 is resolved.
     8  
     9  */
    10  
    11  import (
    12  	"fmt"
    13  	"go/build"
    14  	"log"
    15  	"os"
    16  	"path"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"strings"
    21  )
    22  
    23  var buildContext = build.Default
    24  
    25  var (
    26  	goroot       = filepath.Clean(runtime.GOROOT())
    27  	gorootSrcPkg = filepath.Join(goroot, "src/pkg")
    28  )
    29  
    30  // importPathsNoDotExpansion returns the import paths to use for the given
    31  // command line, but it does no ... expansion.
    32  func importPathsNoDotExpansion(args []string) []string {
    33  	if len(args) == 0 {
    34  		return []string{"."}
    35  	}
    36  	var out []string
    37  	for _, a := range args {
    38  		// Arguments are supposed to be import paths, but
    39  		// as a courtesy to Windows developers, rewrite \ to /
    40  		// in command-line arguments.  Handles .\... and so on.
    41  		if filepath.Separator == '\\' {
    42  			a = strings.Replace(a, `\`, `/`, -1)
    43  		}
    44  
    45  		// Put argument in canonical form, but preserve leading ./.
    46  		if strings.HasPrefix(a, "./") {
    47  			a = "./" + path.Clean(a)
    48  			if a == "./." {
    49  				a = "."
    50  			}
    51  		} else {
    52  			a = path.Clean(a)
    53  		}
    54  		if a == "all" || a == "std" {
    55  			out = append(out, allPackages(a)...)
    56  			continue
    57  		}
    58  		out = append(out, a)
    59  	}
    60  	return out
    61  }
    62  
    63  // importPaths returns the import paths to use for the given command line.
    64  func importPaths(args []string) []string {
    65  	args = importPathsNoDotExpansion(args)
    66  	var out []string
    67  	for _, a := range args {
    68  		if strings.Contains(a, "...") {
    69  			if build.IsLocalImport(a) {
    70  				out = append(out, allPackagesInFS(a)...)
    71  			} else {
    72  				out = append(out, allPackages(a)...)
    73  			}
    74  			continue
    75  		}
    76  		out = append(out, a)
    77  	}
    78  	return out
    79  }
    80  
    81  // matchPattern(pattern)(name) reports whether
    82  // name matches pattern.  Pattern is a limited glob
    83  // pattern in which '...' means 'any string' and there
    84  // is no other special syntax.
    85  func matchPattern(pattern string) func(name string) bool {
    86  	re := regexp.QuoteMeta(pattern)
    87  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
    88  	// Special case: foo/... matches foo too.
    89  	if strings.HasSuffix(re, `/.*`) {
    90  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
    91  	}
    92  	reg := regexp.MustCompile(`^` + re + `$`)
    93  	return func(name string) bool {
    94  		return reg.MatchString(name)
    95  	}
    96  }
    97  
    98  // hasPathPrefix reports whether the path s begins with the
    99  // elements in prefix.
   100  func hasPathPrefix(s, prefix string) bool {
   101  	switch {
   102  	default:
   103  		return false
   104  	case len(s) == len(prefix):
   105  		return s == prefix
   106  	case len(s) > len(prefix):
   107  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   108  			return strings.HasPrefix(s, prefix)
   109  		}
   110  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   111  	}
   112  }
   113  
   114  // treeCanMatchPattern(pattern)(name) reports whether
   115  // name or children of name can possibly match pattern.
   116  // Pattern is the same limited glob accepted by matchPattern.
   117  func treeCanMatchPattern(pattern string) func(name string) bool {
   118  	wildCard := false
   119  	if i := strings.Index(pattern, "..."); i >= 0 {
   120  		wildCard = true
   121  		pattern = pattern[:i]
   122  	}
   123  	return func(name string) bool {
   124  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   125  			wildCard && strings.HasPrefix(name, pattern)
   126  	}
   127  }
   128  
   129  // allPackages returns all the packages that can be found
   130  // under the $GOPATH directories and $GOROOT matching pattern.
   131  // The pattern is either "all" (all packages), "std" (standard packages)
   132  // or a path including "...".
   133  func allPackages(pattern string) []string {
   134  	pkgs := matchPackages(pattern)
   135  	if len(pkgs) == 0 {
   136  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   137  	}
   138  	return pkgs
   139  }
   140  
   141  func matchPackages(pattern string) []string {
   142  	match := func(string) bool { return true }
   143  	treeCanMatch := func(string) bool { return true }
   144  	if pattern != "all" && pattern != "std" {
   145  		match = matchPattern(pattern)
   146  		treeCanMatch = treeCanMatchPattern(pattern)
   147  	}
   148  
   149  	have := map[string]bool{
   150  		"builtin": true, // ignore pseudo-package that exists only for documentation
   151  	}
   152  	if !buildContext.CgoEnabled {
   153  		have["runtime/cgo"] = true // ignore during walk
   154  	}
   155  	var pkgs []string
   156  
   157  	// Commands
   158  	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
   159  	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
   160  		if err != nil || !fi.IsDir() || path == cmd {
   161  			return nil
   162  		}
   163  		name := path[len(cmd):]
   164  		if !treeCanMatch(name) {
   165  			return filepath.SkipDir
   166  		}
   167  		// Commands are all in cmd/, not in subdirectories.
   168  		if strings.Contains(name, string(filepath.Separator)) {
   169  			return filepath.SkipDir
   170  		}
   171  
   172  		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
   173  		name = "cmd/" + name
   174  		if have[name] {
   175  			return nil
   176  		}
   177  		have[name] = true
   178  		if !match(name) {
   179  			return nil
   180  		}
   181  		_, err = buildContext.ImportDir(path, 0)
   182  		if err != nil {
   183  			if _, noGo := err.(*build.NoGoError); !noGo {
   184  				log.Print(err)
   185  			}
   186  			return nil
   187  		}
   188  		pkgs = append(pkgs, name)
   189  		return nil
   190  	})
   191  
   192  	for _, src := range buildContext.SrcDirs() {
   193  		if pattern == "std" && src != gorootSrcPkg {
   194  			continue
   195  		}
   196  		src = filepath.Clean(src) + string(filepath.Separator)
   197  		filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
   198  			if err != nil || !fi.IsDir() || path == src {
   199  				return nil
   200  			}
   201  
   202  			// Avoid .foo, _foo, and testdata directory trees.
   203  			_, elem := filepath.Split(path)
   204  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   205  				return filepath.SkipDir
   206  			}
   207  
   208  			name := filepath.ToSlash(path[len(src):])
   209  			if pattern == "std" && strings.Contains(name, ".") {
   210  				return filepath.SkipDir
   211  			}
   212  			if !treeCanMatch(name) {
   213  				return filepath.SkipDir
   214  			}
   215  			if have[name] {
   216  				return nil
   217  			}
   218  			have[name] = true
   219  			if !match(name) {
   220  				return nil
   221  			}
   222  			_, err = buildContext.ImportDir(path, 0)
   223  			if err != nil {
   224  				if _, noGo := err.(*build.NoGoError); noGo {
   225  					return nil
   226  				}
   227  			}
   228  			pkgs = append(pkgs, name)
   229  			return nil
   230  		})
   231  	}
   232  	return pkgs
   233  }
   234  
   235  // allPackagesInFS is like allPackages but is passed a pattern
   236  // beginning ./ or ../, meaning it should scan the tree rooted
   237  // at the given directory.  There are ... in the pattern too.
   238  func allPackagesInFS(pattern string) []string {
   239  	pkgs := matchPackagesInFS(pattern)
   240  	if len(pkgs) == 0 {
   241  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   242  	}
   243  	return pkgs
   244  }
   245  
   246  func matchPackagesInFS(pattern string) []string {
   247  	// Find directory to begin the scan.
   248  	// Could be smarter but this one optimization
   249  	// is enough for now, since ... is usually at the
   250  	// end of a path.
   251  	i := strings.Index(pattern, "...")
   252  	dir, _ := path.Split(pattern[:i])
   253  
   254  	// pattern begins with ./ or ../.
   255  	// path.Clean will discard the ./ but not the ../.
   256  	// We need to preserve the ./ for pattern matching
   257  	// and in the returned import paths.
   258  	prefix := ""
   259  	if strings.HasPrefix(pattern, "./") {
   260  		prefix = "./"
   261  	}
   262  	match := matchPattern(pattern)
   263  
   264  	var pkgs []string
   265  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   266  		if err != nil || !fi.IsDir() {
   267  			return nil
   268  		}
   269  		if path == dir {
   270  			// filepath.Walk starts at dir and recurses. For the recursive case,
   271  			// the path is the result of filepath.Join, which calls filepath.Clean.
   272  			// The initial case is not Cleaned, though, so we do this explicitly.
   273  			//
   274  			// This converts a path like "./io/" to "io". Without this step, running
   275  			// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
   276  			// package, because prepending the prefix "./" to the unclean path would
   277  			// result in "././io", and match("././io") returns false.
   278  			path = filepath.Clean(path)
   279  		}
   280  
   281  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   282  		_, elem := filepath.Split(path)
   283  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   284  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   285  			return filepath.SkipDir
   286  		}
   287  
   288  		name := prefix + filepath.ToSlash(path)
   289  		if !match(name) {
   290  			return nil
   291  		}
   292  		if _, err = build.ImportDir(path, 0); err != nil {
   293  			if _, noGo := err.(*build.NoGoError); !noGo {
   294  				log.Print(err)
   295  			}
   296  			return nil
   297  		}
   298  		pkgs = append(pkgs, name)
   299  		return nil
   300  	})
   301  	return pkgs
   302  }