diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 11:12:55 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 15:05:30 +0200 |
| commit | c7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch) | |
| tree | 0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/OpenPeeDeeP/depguard/depguard.go | |
| parent | 9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff) | |
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/OpenPeeDeeP/depguard/depguard.go')
| -rw-r--r-- | vendor/github.com/OpenPeeDeeP/depguard/depguard.go | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/vendor/github.com/OpenPeeDeeP/depguard/depguard.go b/vendor/github.com/OpenPeeDeeP/depguard/depguard.go new file mode 100644 index 000000000..1dbffb7d6 --- /dev/null +++ b/vendor/github.com/OpenPeeDeeP/depguard/depguard.go @@ -0,0 +1,241 @@ +package depguard + +import ( + "go/build" + "go/token" + "io/ioutil" + "path" + "sort" + "strings" + + "github.com/gobwas/glob" + "golang.org/x/tools/go/loader" +) + +// ListType states what kind of list is passed in. +type ListType int + +const ( + // LTBlacklist states the list given is a blacklist. (default) + LTBlacklist ListType = iota + // LTWhitelist states the list given is a whitelist. + LTWhitelist +) + +// StringToListType makes it easier to turn a string into a ListType. +// It assumes that the string representation is lower case. +var StringToListType = map[string]ListType{ + "whitelist": LTWhitelist, + "blacklist": LTBlacklist, +} + +// Issue with the package with PackageName at the Position. +type Issue struct { + PackageName string + Position token.Position +} + +// Depguard checks imports to make sure they follow the given list and constraints. +type Depguard struct { + ListType ListType + IncludeGoRoot bool + + Packages []string + prefixPackages []string + globPackages []glob.Glob + + TestPackages []string + prefixTestPackages []string + globTestPackages []glob.Glob + + prefixRoot []string +} + +// Run checks for dependencies given the program and validates them against +// Packages. +func (dg *Depguard) Run(config *loader.Config, prog *loader.Program) ([]*Issue, error) { + // Shortcut execution on an empty blacklist as that means every package is allowed + if dg.ListType == LTBlacklist && len(dg.Packages) == 0 { + return nil, nil + } + + if err := dg.initialize(config, prog); err != nil { + return nil, err + } + directImports, err := dg.createImportMap(prog) + if err != nil { + return nil, err + } + var issues []*Issue + for pkg, positions := range directImports { + for _, pos := range positions { + + prefixList, globList := dg.prefixPackages, dg.globPackages + if len(dg.TestPackages) > 0 && strings.Index(pos.Filename, "_test.go") != -1 { + prefixList, globList = dg.prefixTestPackages, dg.globTestPackages + } + + if dg.flagIt(pkg, prefixList, globList) { + issues = append(issues, &Issue{ + PackageName: pkg, + Position: pos, + }) + } + } + } + return issues, nil +} + +func (dg *Depguard) initialize(config *loader.Config, prog *loader.Program) error { + // parse ordinary guarded packages + for _, pkg := range dg.Packages { + if strings.ContainsAny(pkg, "!?*[]{}") { + g, err := glob.Compile(pkg, '/') + if err != nil { + return err + } + dg.globPackages = append(dg.globPackages, g) + } else { + dg.prefixPackages = append(dg.prefixPackages, pkg) + } + } + + // Sort the packages so we can have a faster search in the array + sort.Strings(dg.prefixPackages) + + // parse guarded tests packages + for _, pkg := range dg.TestPackages { + if strings.ContainsAny(pkg, "!?*[]{}") { + g, err := glob.Compile(pkg, '/') + if err != nil { + return err + } + dg.globTestPackages = append(dg.globTestPackages, g) + } else { + dg.prefixTestPackages = append(dg.prefixTestPackages, pkg) + } + } + + // Sort the test packages so we can have a faster search in the array + sort.Strings(dg.prefixTestPackages) + + if !dg.IncludeGoRoot { + var err error + dg.prefixRoot, err = listRootPrefixs(config.Build) + if err != nil { + return err + } + } + + return nil +} + +func (dg *Depguard) createImportMap(prog *loader.Program) (map[string][]token.Position, error) { + importMap := make(map[string][]token.Position) + // For the directly imported packages + for _, imported := range prog.InitialPackages() { + // Go through their files + for _, file := range imported.Files { + // And populate a map of all direct imports and their positions + // This will filter out GoRoot depending on the Depguard.IncludeGoRoot + for _, fileImport := range file.Imports { + fileImportPath := cleanBasicLitString(fileImport.Path.Value) + if !dg.IncludeGoRoot && dg.isRoot(fileImportPath) { + continue + } + position := prog.Fset.Position(fileImport.Pos()) + positions, found := importMap[fileImportPath] + if !found { + importMap[fileImportPath] = []token.Position{ + position, + } + continue + } + importMap[fileImportPath] = append(positions, position) + } + } + } + return importMap, nil +} + +func pkgInList(pkg string, prefixList []string, globList []glob.Glob) bool { + if pkgInPrefixList(pkg, prefixList) { + return true + } + return pkgInGlobList(pkg, globList) +} + +func pkgInPrefixList(pkg string, prefixList []string) bool { + // Idx represents where in the package slice the passed in package would go + // when sorted. -1 Just means that it would be at the very front of the slice. + idx := sort.Search(len(prefixList), func(i int) bool { + return prefixList[i] > pkg + }) - 1 + // This means that the package passed in has no way to be prefixed by anything + // in the package list as it is already smaller then everything + if idx == -1 { + return false + } + return strings.HasPrefix(pkg, prefixList[idx]) +} + +func pkgInGlobList(pkg string, globList []glob.Glob) bool { + for _, g := range globList { + if g.Match(pkg) { + return true + } + } + return false +} + +// InList | WhiteList | BlackList +// y | | x +// n | x | +func (dg *Depguard) flagIt(pkg string, prefixList []string, globList []glob.Glob) bool { + return pkgInList(pkg, prefixList, globList) == (dg.ListType == LTBlacklist) +} + +func cleanBasicLitString(value string) string { + return strings.Trim(value, "\"\\") +} + +// We can do this as all imports that are not root are either prefixed with a domain +// or prefixed with `./` or `/` to dictate it is a local file reference +func listRootPrefixs(buildCtx *build.Context) ([]string, error) { + if buildCtx == nil { + buildCtx = &build.Default + } + root := path.Join(buildCtx.GOROOT, "src") + fs, err := ioutil.ReadDir(root) + if err != nil { + return nil, err + } + var pkgPrefix []string + for _, f := range fs { + if !f.IsDir() { + continue + } + pkgPrefix = append(pkgPrefix, f.Name()) + } + return pkgPrefix, nil +} + +func (dg *Depguard) isRoot(importPath string) bool { + // Idx represents where in the package slice the passed in package would go + // when sorted. -1 Just means that it would be at the very front of the slice. + idx := sort.Search(len(dg.prefixRoot), func(i int) bool { + return dg.prefixRoot[i] > importPath + }) - 1 + // This means that the package passed in has no way to be prefixed by anything + // in the package list as it is already smaller then everything + if idx == -1 { + return false + } + // if it is prefixed by a root prefix we need to check if it is an exact match + // or prefix with `/` as this could return false posative if the domain was + // `archive.com` for example as `archive` is a go root package. + if strings.HasPrefix(importPath, dg.prefixRoot[idx]) { + return strings.HasPrefix(importPath, dg.prefixRoot[idx]+"/") || importPath == dg.prefixRoot[idx] + } + return false +} |
