gopkg.in/golangci/golangci-lint.v1@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 }