Skip to content

Commit

Permalink
Add -exclude flag and functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
kisielk committed Nov 18, 2016
1 parent 5b29f57 commit 719ac45
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 28 deletions.
22 changes: 19 additions & 3 deletions README.md
Expand Up @@ -37,11 +37,27 @@ The `-blank` flag enables checking for assignments of errors to the
blank identifier. It takes no arguments.


## Whitelist
errcheck has an internal whitelist for functions in the Go standard library that
## Excluding functions

Use the `-exclude` flag to specify a path to a file containing a list of functions to
be excluded.

errcheck -exclude errcheck_excludes.txt path/to/package

The file should contain one function signature per line. The format for function signatures is
`package.FunctionName` while for methods it's `(package.Receiver).MethodName` for value receivers
and `(*package.Receiver).MethodName` for pointer receivers.

An example of an exclude file is:

io/ioutil.ReadFile
(*net/http.Client).Do

The exclude list is combined with an internal list for functions in the Go standard library that
have an error return type but are documented to never return an error.

## Ignoring functions

### The deprecated method

The `-ignore` flag takes a comma-separated list of pairs of the form package:regex.
For each package, the regex describes which functions to ignore within that package.
Expand Down
43 changes: 29 additions & 14 deletions internal/errcheck/errcheck.go
Expand Up @@ -106,6 +106,30 @@ type Checker struct {

// If true, checking of of _test.go files is disabled
WithoutTests bool

exclude map[string]bool
}

func NewChecker() *Checker {
c := Checker{}
c.SetExclude(map[string]bool{})
return &c
}

func (c *Checker) SetExclude(l map[string]bool) {
// Default exclude for stdlib functions
c.exclude = map[string]bool{
"math/rand.Read": true,
"(*math/rand.Rand).Read": true,

"(*bytes.Buffer).Write": true,
"(*bytes.Buffer).WriteByte": true,
"(*bytes.Buffer).WriteRune": true,
"(*bytes.Buffer).WriteString": true,
}
for k := range l {
c.exclude[k] = true
}
}

func (c *Checker) logf(msg string, args ...interface{}) {
Expand Down Expand Up @@ -160,6 +184,7 @@ func (c *Checker) CheckPackages(paths ...string) error {
blank: c.Blank,
asserts: c.Asserts,
lines: make(map[string][]string),
exclude: c.exclude,
errors: []UncheckedError{},
}

Expand All @@ -186,22 +211,12 @@ type visitor struct {
blank bool
asserts bool
lines map[string][]string
exclude map[string]bool

errors []UncheckedError
}

func (v *visitor) builtinIgnoreCall(call *ast.CallExpr) bool {
// TODO(dh): find a better name for this method
ignored := map[string]bool{
"math/rand.Read": true,
"(*math/rand.Rand).Read": true,

"(*bytes.Buffer).Write": true,
"(*bytes.Buffer).WriteByte": true,
"(*bytes.Buffer).WriteRune": true,
"(*bytes.Buffer).WriteString": true,
}

func (v *visitor) excludeCall(call *ast.CallExpr) bool {
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return false
Expand All @@ -219,11 +234,11 @@ func (v *visitor) builtinIgnoreCall(call *ast.CallExpr) bool {
// want to support vendored stdlib packages, we need to implement
// FullName with our own logic.
name := fn.FullName()
return ignored[name]
return v.exclude[name]
}

func (v *visitor) ignoreCall(call *ast.CallExpr) bool {
if v.builtinIgnoreCall(call) {
if v.excludeCall(call) {
return true
}

Expand Down
16 changes: 9 additions & 7 deletions internal/errcheck/errcheck_test.go
Expand Up @@ -89,6 +89,10 @@ func TestAll(t *testing.T) {
test(t, CheckAsserts|CheckBlank)
}

func TestWhitelist(t *testing.T) {

}

const testVendorMain = `
package main
Expand Down Expand Up @@ -152,9 +156,8 @@ func TestIgnore(t *testing.T) {
}

for i, currCase := range cases {
checker := &Checker{
Ignore: currCase.ignore,
}
checker := NewChecker()
checker.Ignore = currCase.ignore
err := checker.CheckPackages(path.Join("github.com/kisielk/errcheck/internal/errcheck", testVendorDir))

if currCase.numExpectedErrs == 0 {
Expand All @@ -181,10 +184,9 @@ func test(t *testing.T, f flags) {
asserts bool = f&CheckAsserts != 0
blank bool = f&CheckBlank != 0
)
checker := &Checker{
Asserts: asserts,
Blank: blank,
}
checker := NewChecker()
checker.Asserts = asserts
checker.Blank = blank
err := checker.CheckPackages(testPackage)
uerr, ok := err.(*UncheckedErrors)
if !ok {
Expand Down
28 changes: 25 additions & 3 deletions main.go
@@ -1,6 +1,7 @@
package main

import (
"bufio"
"flag"
"fmt"
"os"
Expand Down Expand Up @@ -102,7 +103,7 @@ func reportUncheckedErrors(e *errcheck.UncheckedErrors) {
func mainCmd(args []string) int {
runtime.GOMAXPROCS(runtime.NumCPU())

checker := &errcheck.Checker{}
checker := errcheck.NewChecker()
paths, err := parseFlags(checker, args)
if err != exitCodeOk {
return err
Expand Down Expand Up @@ -137,13 +138,34 @@ func parseFlags(checker *errcheck.Checker, args []string) ([]string, int) {
ignore := ignoreFlag(map[string]*regexp.Regexp{
"fmt": dotStar,
})
flags.Var(ignore, "ignore", "comma-separated list of pairs of the form pkg:regex\n"+
" the regex is used to ignore names within pkg")
flags.Var(ignore, "ignore", "[deprecated] comma-separated list of pairs of the form pkg:regex\n"+
" the regex is used to ignore names within pkg.")

var excludeFile string
flags.StringVar(&excludeFile, "exclude", "", "Path to a file containing a list of functions to exclude from checking")

if err := flags.Parse(args[1:]); err != nil {
return nil, exitFatalError
}

if excludeFile != "" {
exclude := make(map[string]bool)
fh, err := os.Open(excludeFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not read exclude file: %s\n", err)
return nil, exitFatalError
}
scanner := bufio.NewScanner(fh)
for scanner.Scan() {
exclude[scanner.Text()] = true
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Could not read exclude file: %s\n", err)
return nil, exitFatalError
}
checker.SetExclude(exclude)
}

checker.Tags = tags
for _, pkg := range strings.Split(*ignorePkg, ",") {
if pkg != "" {
Expand Down
2 changes: 1 addition & 1 deletion main_test.go
Expand Up @@ -54,7 +54,7 @@ func TestMain(t *testing.T) {
t.Errorf("Exit code is %d, expected %d", exitCode, exitUncheckedError)
}

expectUnchecked := 14
expectUnchecked := 15
if got := strings.Count(out, "UNCHECKED"); got != expectUnchecked {
t.Errorf("Got %d UNCHECKED errors, expected %d in:\n%s", got, expectUnchecked, out)
}
Expand Down
3 changes: 3 additions & 0 deletions testdata/main.go
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"fmt"
"io/ioutil"
"math/rand"
mrand "math/rand"
)
Expand Down Expand Up @@ -136,4 +137,6 @@ func main() {
b2.Write(nil)
rand.Read(nil)
mrand.Read(nil)

ioutil.ReadFile("main.go") // UNCHECKED
}

0 comments on commit 719ac45

Please sign in to comment.