
1. Log all warnings, don't hide none of them 2. Write fatal messages (stop analysis) with error log level 3. Remove ugly timestamp counter from logrus output 4. Print nested module prefix in log 5. Make logger abstraction: no global logging anymore 6. Refactor config reading to config.FileReader struct to avoid passing logger into every function 7. Replace exit codes hardcoding with constants in exitcodes package 8. Fail test if any warning was logged 9. Fix calculation of relative path if we analyze parent dir ../ 10. Move Runner initialization from Executor to NewRunner func 11. Log every AST parsing error 12. Properly print used config file path in verbose mode 13. Print package files if only 1 package is analyzedin verbose mode, print not compiling packages in verbose mode 14. Forbid usage of github.com/sirupsen/logrus by DepGuard linter 15. Add default ignore pattern to folint: "comment on exported const"
221 lines
4.7 KiB
Go
221 lines
4.7 KiB
Go
package packages
|
|
|
|
import (
|
|
"fmt"
|
|
"go/build"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
)
|
|
|
|
type Resolver struct {
|
|
excludeDirs map[string]*regexp.Regexp
|
|
buildTags []string
|
|
|
|
skippedDirs []string
|
|
log logutils.Log
|
|
|
|
wd string // working directory
|
|
}
|
|
|
|
func NewResolver(buildTags, excludeDirs []string, log logutils.Log) (*Resolver, error) {
|
|
excludeDirsMap := map[string]*regexp.Regexp{}
|
|
for _, dir := range excludeDirs {
|
|
re, err := regexp.Compile(dir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't compile regexp %q: %s", dir, err)
|
|
}
|
|
|
|
excludeDirsMap[dir] = re
|
|
}
|
|
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't get working dir: %s", err)
|
|
}
|
|
|
|
return &Resolver{
|
|
excludeDirs: excludeDirsMap,
|
|
buildTags: buildTags,
|
|
log: log,
|
|
wd: wd,
|
|
}, nil
|
|
}
|
|
|
|
func (r Resolver) isIgnoredDir(dir string) bool {
|
|
cleanName := filepath.Clean(dir)
|
|
|
|
dirName := filepath.Base(cleanName)
|
|
|
|
// https://github.com/golang/dep/issues/298
|
|
// https://github.com/tools/godep/issues/140
|
|
if strings.HasPrefix(dirName, ".") && dirName != "." && dirName != ".." {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(dirName, "_") {
|
|
return true
|
|
}
|
|
|
|
for _, dirExludeRe := range r.excludeDirs {
|
|
if dirExludeRe.MatchString(cleanName) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (r *Resolver) resolveRecursively(root string, prog *Program) error {
|
|
// import root
|
|
if err := r.resolveDir(root, prog); err != nil {
|
|
return err
|
|
}
|
|
|
|
fis, err := ioutil.ReadDir(root)
|
|
if err != nil {
|
|
return fmt.Errorf("can't resolve dir %s: %s", root, err)
|
|
}
|
|
// TODO: pass cached fis to build.Context
|
|
|
|
for _, fi := range fis {
|
|
if !fi.IsDir() {
|
|
// ignore files: they were already imported by resolveDir(root)
|
|
continue
|
|
}
|
|
|
|
subdir := filepath.Join(root, fi.Name())
|
|
|
|
// Normalize each subdir because working directory can be one of these subdirs:
|
|
// working dir = /app/subdir, resolve root is ../, without this normalization
|
|
// path of subdir will be "../subdir" but it must be ".".
|
|
// Normalize path before checking is ignored dir.
|
|
subdir, err := r.normalizePath(subdir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.isIgnoredDir(subdir) {
|
|
r.skippedDirs = append(r.skippedDirs, subdir)
|
|
continue
|
|
}
|
|
|
|
if err := r.resolveRecursively(subdir, prog); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r Resolver) resolveDir(dir string, prog *Program) error {
|
|
// TODO: fork build.Import to reuse AST parsing
|
|
bp, err := prog.bctx.ImportDir(dir, build.ImportComment|build.IgnoreVendor)
|
|
if err != nil {
|
|
if _, nogo := err.(*build.NoGoError); nogo {
|
|
// Don't complain if the failure is due to no Go source files.
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("can't resolve dir %s: %s", dir, err)
|
|
}
|
|
|
|
pkg := Package{
|
|
bp: bp,
|
|
}
|
|
prog.addPackage(&pkg)
|
|
return nil
|
|
}
|
|
|
|
func (r Resolver) addFakePackage(filePath string, prog *Program) {
|
|
// Don't take build tags, is it test file or not, etc
|
|
// into account. If user explicitly wants to analyze this file
|
|
// do it.
|
|
p := Package{
|
|
bp: &build.Package{
|
|
GoFiles: []string{filePath},
|
|
},
|
|
isFake: true,
|
|
}
|
|
prog.addPackage(&p)
|
|
}
|
|
|
|
func (r Resolver) Resolve(paths ...string) (prog *Program, err error) {
|
|
startedAt := time.Now()
|
|
defer func() {
|
|
r.log.Infof("Paths resolving took %s: %s", time.Since(startedAt), prog)
|
|
}()
|
|
|
|
if len(paths) == 0 {
|
|
return nil, fmt.Errorf("no paths are set")
|
|
}
|
|
|
|
bctx := build.Default
|
|
bctx.BuildTags = append(bctx.BuildTags, r.buildTags...)
|
|
prog = &Program{
|
|
bctx: bctx,
|
|
}
|
|
|
|
for _, path := range paths {
|
|
if err := r.resolvePath(path, prog); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if len(r.skippedDirs) != 0 {
|
|
r.log.Infof("Skipped dirs: %s", r.skippedDirs)
|
|
}
|
|
|
|
return prog, nil
|
|
}
|
|
|
|
func (r *Resolver) normalizePath(path string) (string, error) {
|
|
return fsutils.ShortestRelPath(path, r.wd)
|
|
}
|
|
|
|
func (r *Resolver) resolvePath(path string, prog *Program) error {
|
|
needRecursive := strings.HasSuffix(path, "/...")
|
|
if needRecursive {
|
|
path = filepath.Dir(path)
|
|
}
|
|
|
|
evalPath, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
return fmt.Errorf("can't eval symlinks for path %s: %s", path, err)
|
|
}
|
|
path = evalPath
|
|
|
|
path, err = r.normalizePath(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if needRecursive {
|
|
if err = r.resolveRecursively(path, prog); err != nil {
|
|
return fmt.Errorf("can't recursively resolve %s: %s", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
return fmt.Errorf("can't find path %s: %s", path, err)
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
if err := r.resolveDir(path, prog); err != nil {
|
|
return fmt.Errorf("can't resolve dir %s: %s", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
r.addFakePackage(path, prog)
|
|
return nil
|
|
}
|