Skip to content

Commit

Permalink
Ignore File Rules (#21)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
timkral authored Jan 4, 2022
1 parent 0a0c564 commit 6b0e4d4
Show file tree
Hide file tree
Showing 6 changed files with 638 additions and 14 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions cmd/depguard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down
78 changes: 68 additions & 10 deletions depguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,6 +56,10 @@ type Depguard struct {
prefixTestPackages []string
globTestPackages []glob.Glob

IgnoreFileRules []string
prefixIgnoreFileRules []string
globIgnoreFileRules []negatableGlob

prefixRoot []string
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand Down
Loading

0 comments on commit 6b0e4d4

Please sign in to comment.