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