From 6b0e4d4306e8710b435a1636efe2485b13457321 Mon Sep 17 00:00:00 2001 From: Tim Kral Date: Tue, 4 Jan 2022 14:52:04 -0800 Subject: [PATCH] Ignore File Rules (#21) * Add basic tests * Add tests for mixed allow/deny and test files * Prevent flakey test results * Add tests for go root packages * Introduce ignore file rules * Allow multiple files in test programs * Add support for negation in ignore file rules * Add tests for ignore file rules * Better test names * Enable ignore fule rules in CLI config * Update README * Small README tweaks * README edit * Move ignore file check ahead of all other checks --- README.md | 33 +++ cmd/depguard/main.go | 10 +- depguard.go | 78 ++++++- depguard_test.go | 520 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 10 + 6 files changed, 638 insertions(+), 14 deletions(-) create mode 100644 depguard_test.go diff --git a/README.md b/README.md index 8352cdf..b942275 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,39 @@ The following is an example configuration file. - Set `includeGoStdLib` (`includeGoRoot` for backwards compatibility) to true if you want to check the list against standard lib. If not specified the default is false. +### Ignore File Rules + +The configuration also allows us to specify rules to ignore certain files considered by the linter. This means that we need not apply package import checks across our entire code base. + +For example, consider the following configuration to block a test package: +```json +{ + "type": "denylist", + "packages": ["github.com/stretchr/testify"], + "inTests": ["github.com/stretchr/testify"] +} +``` + +We can use a `ignoreFileRules` field to write a configuration that only considers test files: +```json +{ + "type": "denylist", + "packages": ["github.com/stretchr/testify"], + "ignoreFileRules": ["!**/*_test.go"] +} +``` + +Or if we wanted to consider only non-test files: +```json +{ + "type": "denylist", + "packages": ["github.com/stretchr/testify"], + "ignoreFileRules": ["**/*_test.go"] +} +``` + +Like the `packages` field, the `ignoreFileRules` field can accept both string prefixes and string glob patterns. Note in the first example above, the use of the `!` character in front of the rule. This is a special character which signals that the linter should negate the rule. This allows for more precise control, but it is only available for glob patterns. + ## Gometalinter The binary installation of this linter can be used with diff --git a/cmd/depguard/main.go b/cmd/depguard/main.go index 6a195f9..7d28829 100644 --- a/cmd/depguard/main.go +++ b/cmd/depguard/main.go @@ -43,6 +43,7 @@ type config struct { IncludeGoRoot bool `json:"includeGoRoot"` IncludeGoStdLib bool `json:"includeGoStdLib"` InTests []string `json:"inTests"` + IgnoreFileRules []string `json:"ignoreFileRules"` listType depguard.ListType } @@ -118,10 +119,11 @@ func main() { log.Fatalln(err) } dg := &depguard.Depguard{ - Packages: config.Packages, - IncludeGoRoot: config.IncludeGoRoot, - ListType: config.listType, - TestPackages: config.InTests, + Packages: config.Packages, + IncludeGoRoot: config.IncludeGoRoot, + ListType: config.listType, + TestPackages: config.InTests, + IgnoreFileRules: config.IgnoreFileRules, } issues, err := dg.Run(conf, prog) if err != nil { diff --git a/depguard.go b/depguard.go index c1b7340..b727549 100644 --- a/depguard.go +++ b/depguard.go @@ -37,6 +37,12 @@ type Issue struct { Position token.Position } +// Wrapper for glob patterns that allows for custom negation +type negatableGlob struct { + g glob.Glob + negate bool +} + // Depguard checks imports to make sure they follow the given list and constraints. type Depguard struct { ListType ListType @@ -50,6 +56,10 @@ type Depguard struct { prefixTestPackages []string globTestPackages []glob.Glob + IgnoreFileRules []string + prefixIgnoreFileRules []string + globIgnoreFileRules []negatableGlob + prefixRoot []string } @@ -71,6 +81,9 @@ func (dg *Depguard) Run(config *loader.Config, prog *loader.Program) ([]*Issue, var issues []*Issue for pkg, positions := range directImports { for _, pos := range positions { + if ignoreFile(pos.Filename, dg.prefixIgnoreFileRules, dg.globIgnoreFileRules) { + continue + } prefixList, globList := dg.prefixPackages, dg.globPackages if len(dg.TestPackages) > 0 && strings.Index(pos.Filename, "_test.go") != -1 { @@ -121,6 +134,32 @@ func (dg *Depguard) initialize(config *loader.Config, prog *loader.Program) erro // Sort the test packages so we can have a faster search in the array sort.Strings(dg.prefixTestPackages) + // parse ignore file rules + for _, rule := range dg.IgnoreFileRules { + if strings.ContainsAny(rule, "!?*[]{}") { + ng := negatableGlob{} + if strings.HasPrefix(rule, "!") { + ng.negate = true + rule = rule[1:] // Strip out the leading '!' + } else { + ng.negate = false + } + + g, err := glob.Compile(rule, '/') + if err != nil { + return err + } + ng.g = g + + dg.globIgnoreFileRules = append(dg.globIgnoreFileRules, ng) + } else { + dg.prefixIgnoreFileRules = append(dg.prefixIgnoreFileRules, rule) + } + } + + // Sort the rules so we can have a faster search in the array + sort.Strings(dg.prefixIgnoreFileRules) + if !dg.IncludeGoRoot { var err error dg.prefixRoot, err = listRootPrefixs(config.Build) @@ -160,30 +199,49 @@ func (dg *Depguard) createImportMap(prog *loader.Program) (map[string][]token.Po return importMap, nil } +func ignoreFile(filename string, prefixList []string, negatableGlobList []negatableGlob) bool { + if strInPrefixList(filename, prefixList) { + return true + } + return strInNegatableGlobList(filename, negatableGlobList) +} + func pkgInList(pkg string, prefixList []string, globList []glob.Glob) bool { - if pkgInPrefixList(pkg, prefixList) { + if strInPrefixList(pkg, prefixList) { return true } - return pkgInGlobList(pkg, globList) + return strInGlobList(pkg, globList) } -func pkgInPrefixList(pkg string, prefixList []string) bool { - // Idx represents where in the package slice the passed in package would go +func strInPrefixList(str string, prefixList []string) bool { + // Idx represents where in the prefix slice the passed in string 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 + return prefixList[i] > str }) - 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 + // This means that the string passed in has no way to be prefixed by anything + // in the prefix list as it is already smaller then everything if idx == -1 { return false } - return strings.HasPrefix(pkg, prefixList[idx]) + return strings.HasPrefix(str, prefixList[idx]) } -func pkgInGlobList(pkg string, globList []glob.Glob) bool { +func strInGlobList(str string, globList []glob.Glob) bool { for _, g := range globList { - if g.Match(pkg) { + if g.Match(str) { + return true + } + } + return false +} + +func strInNegatableGlobList(str string, negatableGlobList []negatableGlob) bool { + for _, ng := range negatableGlobList { + // Return true when: + // - Match is true and negate is off + // - Match is false and negate is on + if ng.g.Match(str) != ng.negate { return true } } diff --git a/depguard_test.go b/depguard_test.go new file mode 100644 index 0000000..fe0c456 --- /dev/null +++ b/depguard_test.go @@ -0,0 +1,520 @@ +package depguard_test + +import ( + "go/ast" + "go/token" + "sort" + "strings" + "testing" + + "github.com/OpenPeeDeeP/depguard" + "github.com/stretchr/testify/require" + "golang.org/x/tools/go/loader" +) + +// ========== AllowList ========== + +func TestBasicAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "allow")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestPrefixAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "allow/a", "allow/b")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestGlobAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow/**/pkg"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "allow/a/pkg", "allow/b/c/pkg")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestMixedAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "allow/a", "deny/a")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny/a", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestBasicTestPackagesAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + TestPackages: []string{"allowtest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "allowtest")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestPrefixTestPackagesAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + TestPackages: []string{"allowtest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "allowtest/a", "allowtest/b")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestGlobTestPackagesAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + TestPackages: []string{"allowtest/**/pkg"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "allowtest/a/pkg", "allowtest/b/c/pkg")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestMixedTestPackagesAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + TestPackages: []string{"allowtest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "allowtest/a", "denytest/a")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "denytest/a", issues[0].PackageName) + require.Equal(t, "file_test.go", issues[0].Position.Filename) +} + +func TestExcludeGoRootAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "go/ast")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestIncludeGoRootAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IncludeGoRoot: true, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "go/ast")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "go/ast", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestBasicIgnoreFilesRuleAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IgnoreFileRules: []string{"ignore.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore.go"] = []string{"allow", "deny"} + filesAndPackagePaths["file.go"] = []string{"allow", "deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestPrefixIgnoreFilesRuleAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IgnoreFileRules: []string{"ignore"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore/file.go"] = []string{"allow", "deny"} + filesAndPackagePaths["file.go"] = []string{"allow", "deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestGlobIgnoreFilesRuleAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IgnoreFileRules: []string{"ignore/**/*.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore/a/file.go"] = []string{"allow", "deny"} + filesAndPackagePaths["file.go"] = []string{"allow", "deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestNegateGlobIgnoreFilesRuleAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IgnoreFileRules: []string{"!**/keep/*.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["pkg/ignore/file.go"] = []string{"allow", "deny"} + filesAndPackagePaths["pkg/keep/file.go"] = []string{"allow", "deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "pkg/keep/file.go", issues[0].Position.Filename) +} + +// NOTE: This is semantically equivalent to using the TestPackages configuration +func TestNonTestIgnoreFilesRuleAllowList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTWhitelist, + Packages: []string{"allow"}, + IgnoreFileRules: []string{"!**/*_test.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["pkg/file.go"] = []string{"allow", "deny"} + filesAndPackagePaths["pkg/file_test.go"] = []string{"allow", "deny"} + filesAndPackagePaths["pkg/file_unit_test.go"] = []string{"allow", "deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "pkg/file_test.go", issues[0].Position.Filename) + require.Equal(t, "deny", issues[1].PackageName) + require.Equal(t, "pkg/file_unit_test.go", issues[1].Position.Filename) +} + +// ========== DenyList ========== + +func TestBasicDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "deny")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestPrefixDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "deny/a", "deny/b")) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "deny/a", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) + require.Equal(t, "deny/b", issues[1].PackageName) + require.Equal(t, "file.go", issues[1].Position.Filename) +} + +func TestGlobDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny/**/pkg"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "deny/a/pkg", "deny/b/c/pkg")) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "deny/a/pkg", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) + require.Equal(t, "deny/b/c/pkg", issues[1].PackageName) + require.Equal(t, "file.go", issues[1].Position.Filename) +} + +func TestMixedDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "allow/a", "deny/a")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny/a", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestBasicTestPackagesDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, // NOTE: Linter will shortcut with no package deny list + TestPackages: []string{"denytest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "denytest")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "denytest", issues[0].PackageName) + require.Equal(t, "file_test.go", issues[0].Position.Filename) +} + +func TestPrefixTestPackagesDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, // NOTE: Linter will shortcut with no package deny list + TestPackages: []string{"denytest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "denytest/a", "denytest/b")) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "denytest/a", issues[0].PackageName) + require.Equal(t, "file_test.go", issues[0].Position.Filename) + require.Equal(t, "denytest/b", issues[1].PackageName) + require.Equal(t, "file_test.go", issues[1].Position.Filename) +} + +func TestGlobTestPackagesDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, // NOTE: Linter will shortcut with no package deny list + TestPackages: []string{"denytest/**/pkg"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "denytest/a/pkg", "denytest/b/c/pkg")) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "denytest/a/pkg", issues[0].PackageName) + require.Equal(t, "file_test.go", issues[0].Position.Filename) + require.Equal(t, "denytest/b/c/pkg", issues[1].PackageName) + require.Equal(t, "file_test.go", issues[1].Position.Filename) +} + +func TestMixedTestPackagesDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, // NOTE: Linter will shortcut with no package deny list + TestPackages: []string{"denytest"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file_test.go", "allowtest/a", "denytest/a")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "denytest/a", issues[0].PackageName) + require.Equal(t, "file_test.go", issues[0].Position.Filename) +} + +func TestExcludeGoRootDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"go/ast"}, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "go/ast")) + require.NoError(t, err) + require.Len(t, issues, 0) +} + +func TestIncludeGoRootDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"go/ast"}, + IncludeGoRoot: true, + } + + issues, err := dg.Run(newLoadConfig(), newSimpleProgram("file.go", "go/ast")) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "go/ast", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestBasicIgnoreFilesRuleDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + IgnoreFileRules: []string{"ignore.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore.go"] = []string{"deny"} + filesAndPackagePaths["file.go"] = []string{"deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestPrefixIgnoreFilesRuleDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + IgnoreFileRules: []string{"ignore"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore/file.go"] = []string{"deny"} + filesAndPackagePaths["file.go"] = []string{"deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestGlobIgnoreFilesRuleDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + IgnoreFileRules: []string{"ignore/**/*.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["ignore/a/file.go"] = []string{"deny"} + filesAndPackagePaths["file.go"] = []string{"deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "file.go", issues[0].Position.Filename) +} + +func TestNegateGlobIgnoreFilesRuleDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + IgnoreFileRules: []string{"!**/keep/*.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["pkg/ignore/file.go"] = []string{"deny"} + filesAndPackagePaths["pkg/keep/file.go"] = []string{"deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 1) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "pkg/keep/file.go", issues[0].Position.Filename) +} + +// NOTE: This is semantically equivalent to using the TestPackages configuration +func TestNonTestIgnoreFilesRuleDenyList(t *testing.T) { + dg := depguard.Depguard{ + ListType: depguard.LTBlacklist, + Packages: []string{"deny"}, + IgnoreFileRules: []string{"!**/*_test.go"}, + } + + filesAndPackagePaths := make(map[string][]string, 0) + filesAndPackagePaths["pkg/file.go"] = []string{"deny"} + filesAndPackagePaths["pkg/file_test.go"] = []string{"deny"} + filesAndPackagePaths["pkg/file_unit_test.go"] = []string{"deny"} + issues, err := dg.Run(newLoadConfig(), newProgram(filesAndPackagePaths)) + require.NoError(t, err) + require.Len(t, issues, 2) + sortIssues(issues) + require.Equal(t, "deny", issues[0].PackageName) + require.Equal(t, "pkg/file_test.go", issues[0].Position.Filename) + require.Equal(t, "deny", issues[1].PackageName) + require.Equal(t, "pkg/file_unit_test.go", issues[1].Position.Filename) +} + +func newLoadConfig() *loader.Config { + return &loader.Config{ + Cwd: "", + Build: nil, + } +} + +func newSimpleProgram(fileName string, packagePaths ...string) *loader.Program { + filesAndPackagePaths := make(map[string][]string, 1) + filesAndPackagePaths[fileName] = packagePaths + return newProgram(filesAndPackagePaths) +} + +func newProgram(filesAndPackagePaths map[string][]string) *loader.Program { + var astFiles []*ast.File + progFileSet := token.NewFileSet() + + programCounter := 1 + for fileName, packagePaths := range filesAndPackagePaths { + // Build up a mini AST of the information we need to run the linter + var packageImports []*ast.ImportSpec + for i, _ := range packagePaths { + packagePath := packagePaths[i] + packageImports = append(packageImports, &ast.ImportSpec{ + Path: &ast.BasicLit{ + ValuePos: token.Pos(programCounter + i), + Kind: token.STRING, + Value: packagePath, + }, + }) + } + + astFiles = append(astFiles, &ast.File{ + Imports: packageImports, + }) + + progFileSet.AddFile(fileName, programCounter, len(packageImports)) + programCounter += len(packageImports) + 1 + } + + return &loader.Program{ + Created: []*loader.PackageInfo{ + { + Pkg: nil, + Importable: true, + TransitivelyErrorFree: true, + + Files: astFiles, + Errors: nil, + }, + }, + Fset: progFileSet, + } +} + +func sortIssues(issues []*depguard.Issue) { + sort.Slice(issues, func(i, j int) bool { + return strings.Compare(issues[i].PackageName, issues[j].PackageName) < 0 + }) +} diff --git a/go.mod b/go.mod index 5ad37ed..68daf00 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.13 require ( github.com/gobwas/glob v0.2.3 github.com/kisielk/gotool v1.0.0 + github.com/stretchr/testify v1.7.0 // indirect golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b ) diff --git a/go.sum b/go.sum index 24693c3..11a8c1c 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b h1:7tibmaEqrQYA+q6ri7NQjuxqSwechjtDHKq6/e85S38= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=