github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/golang.org/x/tools/go/buildutil/util.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package buildutil 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "go/parser" 12 "go/token" 13 "io" 14 "io/ioutil" 15 "os" 16 "path" 17 "path/filepath" 18 "strings" 19 ) 20 21 // ParseFile behaves like parser.ParseFile, 22 // but uses the build context's file system interface, if any. 23 // 24 // If file is not absolute (as defined by IsAbsPath), the (dir, file) 25 // components are joined using JoinPath; dir must be absolute. 26 // 27 // The displayPath function, if provided, is used to transform the 28 // filename that will be attached to the ASTs. 29 // 30 // TODO(adonovan): call this from go/loader.parseFiles when the tree thaws. 31 // 32 func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) { 33 if !IsAbsPath(ctxt, file) { 34 file = JoinPath(ctxt, dir, file) 35 } 36 rd, err := OpenFile(ctxt, file) 37 if err != nil { 38 return nil, err 39 } 40 defer rd.Close() // ignore error 41 if displayPath != nil { 42 file = displayPath(file) 43 } 44 return parser.ParseFile(fset, file, rd, mode) 45 } 46 47 // ContainingPackage returns the package containing filename. 48 // 49 // If filename is not absolute, it is interpreted relative to working directory dir. 50 // All I/O is via the build context's file system interface, if any. 51 // 52 // The '...Files []string' fields of the resulting build.Package are not 53 // populated (build.FindOnly mode). 54 // 55 func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) { 56 if !IsAbsPath(ctxt, filename) { 57 filename = JoinPath(ctxt, dir, filename) 58 } 59 60 // We must not assume the file tree uses 61 // "/" always, 62 // `\` always, 63 // or os.PathSeparator (which varies by platform), 64 // but to make any progress, we are forced to assume that 65 // paths will not use `\` unless the PathSeparator 66 // is also `\`, thus we can rely on filepath.ToSlash for some sanity. 67 68 dirSlash := path.Dir(filepath.ToSlash(filename)) + "/" 69 70 // We assume that no source root (GOPATH[i] or GOROOT) contains any other. 71 for _, srcdir := range ctxt.SrcDirs() { 72 srcdirSlash := filepath.ToSlash(srcdir) + "/" 73 if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok { 74 return ctxt.Import(importPath, dir, build.FindOnly) 75 } 76 } 77 78 return nil, fmt.Errorf("can't find package containing %s", filename) 79 } 80 81 // -- Effective methods of file system interface ------------------------- 82 83 // (go/build.Context defines these as methods, but does not export them.) 84 85 // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses 86 // the local file system to answer the question. 87 func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) { 88 if f := ctxt.HasSubdir; f != nil { 89 return f(root, dir) 90 } 91 92 // Try using paths we received. 93 if rel, ok = hasSubdir(root, dir); ok { 94 return 95 } 96 97 // Try expanding symlinks and comparing 98 // expanded against unexpanded and 99 // expanded against expanded. 100 rootSym, _ := filepath.EvalSymlinks(root) 101 dirSym, _ := filepath.EvalSymlinks(dir) 102 103 if rel, ok = hasSubdir(rootSym, dir); ok { 104 return 105 } 106 if rel, ok = hasSubdir(root, dirSym); ok { 107 return 108 } 109 return hasSubdir(rootSym, dirSym) 110 } 111 112 func hasSubdir(root, dir string) (rel string, ok bool) { 113 const sep = string(filepath.Separator) 114 root = filepath.Clean(root) 115 if !strings.HasSuffix(root, sep) { 116 root += sep 117 } 118 119 dir = filepath.Clean(dir) 120 if !strings.HasPrefix(dir, root) { 121 return "", false 122 } 123 124 return filepath.ToSlash(dir[len(root):]), true 125 } 126 127 // FileExists returns true if the specified file exists, 128 // using the build context's file system interface. 129 func FileExists(ctxt *build.Context, path string) bool { 130 if ctxt.OpenFile != nil { 131 r, err := ctxt.OpenFile(path) 132 if err != nil { 133 return false 134 } 135 r.Close() // ignore error 136 return true 137 } 138 _, err := os.Stat(path) 139 return err == nil 140 } 141 142 // OpenFile behaves like os.Open, 143 // but uses the build context's file system interface, if any. 144 func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) { 145 if ctxt.OpenFile != nil { 146 return ctxt.OpenFile(path) 147 } 148 return os.Open(path) 149 } 150 151 // IsAbsPath behaves like filepath.IsAbs, 152 // but uses the build context's file system interface, if any. 153 func IsAbsPath(ctxt *build.Context, path string) bool { 154 if ctxt.IsAbsPath != nil { 155 return ctxt.IsAbsPath(path) 156 } 157 return filepath.IsAbs(path) 158 } 159 160 // JoinPath behaves like filepath.Join, 161 // but uses the build context's file system interface, if any. 162 func JoinPath(ctxt *build.Context, path ...string) string { 163 if ctxt.JoinPath != nil { 164 return ctxt.JoinPath(path...) 165 } 166 return filepath.Join(path...) 167 } 168 169 // IsDir behaves like os.Stat plus IsDir, 170 // but uses the build context's file system interface, if any. 171 func IsDir(ctxt *build.Context, path string) bool { 172 if ctxt.IsDir != nil { 173 return ctxt.IsDir(path) 174 } 175 fi, err := os.Stat(path) 176 return err == nil && fi.IsDir() 177 } 178 179 // ReadDir behaves like ioutil.ReadDir, 180 // but uses the build context's file system interface, if any. 181 func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) { 182 if ctxt.ReadDir != nil { 183 return ctxt.ReadDir(path) 184 } 185 return ioutil.ReadDir(path) 186 } 187 188 // SplitPathList behaves like filepath.SplitList, 189 // but uses the build context's file system interface, if any. 190 func SplitPathList(ctxt *build.Context, s string) []string { 191 if ctxt.SplitPathList != nil { 192 return ctxt.SplitPathList(s) 193 } 194 return filepath.SplitList(s) 195 } 196 197 // sameFile returns true if x and y have the same basename and denote 198 // the same file. 199 // 200 func sameFile(x, y string) bool { 201 if path.Clean(x) == path.Clean(y) { 202 return true 203 } 204 if filepath.Base(x) == filepath.Base(y) { // (optimisation) 205 if xi, err := os.Stat(x); err == nil { 206 if yi, err := os.Stat(y); err == nil { 207 return os.SameFile(xi, yi) 208 } 209 } 210 } 211 return false 212 }