diff --git a/README.md b/README.md index b4bbf58..257ea09 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,31 @@ forbidigo [flags...] patterns... -- packages... -If no patterns are specified, the default pattern of `^(fmt\.Print.*|print|println)$` is used to eliminate debug statements. By default, +If no patterns are specified, the default patterns of `^(Pfmt)\.Print(|f|ln)$` and `^(print|println)$` are used to eliminate debug statements. By default, functions (and whole files), that are identifies as Godoc examples (https://blog.golang.org/examples) are excluded from checking. +By default, patterns get matched against the actual expression as it appears in +the source code. The effect is that ``^fmt\.Print.*$` will not match when that +package gets imported with `import fmt2 "fmt"` and then the function gets +called with `fmt2.Print`. + +This makes it hard to match packages that may get imported under a variety of +different names, for example because there is no established convention or the +name is so generic that import aliases have to be used. To solve this, a +package that contains a subgroup literally called `pkg` (`(?P...)`) will be +matched against text where the import name got replaced with the full package +name (e.g. `example.com/some/pkg`) if such a substitution is possible for the +current expression. Otherwise such a pattern is ignored. + A larger set of interesting patterns might include: -* `^fmt\.Print.*$` -- forbid use of Print statements because they are likely just for debugging -* `^fmt\.Errorf$` -- forbid Errorf in favor of using github.com/pkg/errors -* `^ginkgo\.F[A-Z].*$` -- forbid ginkgo focused commands (used for debug issues) -* `^spew\.Dump$` -- forbid dumping detailed data to stdout -* `^fmt\.Errorf(# please use github\.com/pkg/errors)?$` -- forbid Errorf, with a custom message +* `^(?Pfmt)\.Print.*$` -- forbid use of Print statements because they are likely just for debugging +* `^(?Pfmt)\.Errorf$` -- forbid Errorf in favor of using github.com/pkg/errors +* `^(?Pginkgo)\.F[A-Z].*$` -- forbid ginkgo focused commands (used for debug issues) +* `^(?Pspew)\.Dump$` -- forbid dumping detailed data to stdout +* `^(?Pfmt)\.Errorf(# please use github\.com/pkg/errors)?$` -- forbid Errorf, with a custom message -Note that the linter has no knowledge of what packages were actually imported, so aliased imports will match these patterns. ### Flags - **-set_exit_status** (default false) - Set exit status to 1 if any issues are found. diff --git a/examples/expected_results.txt b/examples/expected_results.txt index 7bedf18..fd213e8 100644 --- a/examples/expected_results.txt +++ b/examples/expected_results.txt @@ -1,3 +1,3 @@ -use of `fmt.Println` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` at CURDIR/examples/example.go:6:2 -use of `print` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` at CURDIR/examples/example.go:8:2 -use of `println` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` at CURDIR/examples/example.go:9:2 +use of `fmt.Println` forbidden by pattern `^(?Pfmt)\.Print(|f|ln)$` at CURDIR/examples/example.go:6:2 +use of `print` forbidden by pattern `^(print|println)$` at CURDIR/examples/example.go:8:2 +use of `println` forbidden by pattern `^(print|println)$` at CURDIR/examples/example.go:9:2 diff --git a/forbidigo/forbidigo.go b/forbidigo/forbidigo.go index 9b7d46b..eb3aa2f 100644 --- a/forbidigo/forbidigo.go +++ b/forbidigo/forbidigo.go @@ -7,6 +7,7 @@ import ( "go/ast" "go/printer" "go/token" + "go/types" "log" "regexp" "strings" @@ -57,7 +58,7 @@ type Linter struct { } func DefaultPatterns() []string { - return []string{`^(fmt\.Print(|f|ln)|print|println)$`} + return []string{`^(?Pfmt)\.Print(|f|ln)$`, `^(print|println)$`} } //go:generate go-options config @@ -97,12 +98,19 @@ type visitor struct { linter *Linter comments []*ast.CommentGroup - fset *token.FileSet - issues []Issue + fset *token.FileSet + typesInfo *types.Info + issues []Issue } +// Deprecated: Run was the original entrypoint before RunWithTypes was introduced to support matching +// full package names. func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { - var issues []Issue + return l.RunWithTypes(fset, nil, nodes...) +} + +func (l *Linter) RunWithTypes(fset *token.FileSet, typesInfo *types.Info, nodes ...ast.Node) ([]Issue, error) { + var issues []Issue for _, node := range nodes { var comments []*ast.CommentGroup isTestFile := false @@ -146,6 +154,7 @@ func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { isTestFile: isTestFile, linter: l, fset: fset, + typesInfo: typesInfo, comments: comments, } ast.Walk(&visitor, node) @@ -155,6 +164,7 @@ func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { } func (v *visitor) Visit(node ast.Node) ast.Visitor { + var selectorExpr *ast.SelectorExpr switch node := node.(type) { case *ast.FuncDecl: // don't descend into godoc examples if we are ignoring them @@ -164,14 +174,57 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor { } return v case *ast.SelectorExpr: + selectorExpr = node case *ast.Ident: default: return v } + + // The text as it appears in the source is always used because issues + // use that. The other texts to match against are extracted when needed + // by a pattern. + srcText := v.textFor(node) + pkgText := "" for _, p := range v.linter.patterns { - if p.pattern.MatchString(v.textFor(node)) && !v.permit(node) { + if p.matchPackage && pkgText == "" { + if v.typesInfo == nil { + continue + } + if selectorExpr == nil { + // Not a selector at all. + continue + } + selector := selectorExpr.X + ident, ok := selector.(*ast.Ident) + if !ok { + // Not an identifier. + continue + } + object, ok := v.typesInfo.Uses[ident] + if !ok { + // No information about the identifier. Should + // not happen, but perhaps there were compile + // errors? + continue + } + pkgName, ok := object.(*types.PkgName) + if !ok { + // No package name, cannot match. + continue + } + pkgText = pkgName.Imported().Path() + "." + selectorExpr.Sel.Name + } + + matchText := "" + switch { + case p.matchPackage: + matchText = pkgText + default: + matchText = srcText + } + if p.pattern.MatchString(matchText) && !v.permit(node) { v.issues = append(v.issues, UsedIssue{ - identifier: v.textFor(node), + identifier: srcText, // Always report the expression as it appears in the source code. pattern: p.pattern.String(), pos: node.Pos(), position: v.fset.Position(node.Pos()), @@ -182,6 +235,7 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor { return nil } +// textFor returns the function as it appears in the source code (= .). func (v *visitor) textFor(node ast.Node) string { buf := new(bytes.Buffer) if err := printer.Fprint(buf, v.fset, node); err != nil { diff --git a/forbidigo/forbidigo_test.go b/forbidigo/forbidigo_test.go index d8b0f8d..f37b563 100644 --- a/forbidigo/forbidigo_test.go +++ b/forbidigo/forbidigo_test.go @@ -1,11 +1,16 @@ package forbidigo import ( - "go/parser" - "go/token" + "go/ast" + "log" + "os" + "path" + "regexp" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/tools/go/packages" ) func TestForbiddenIdentifiers(t *testing.T) { @@ -19,6 +24,18 @@ func foo() { }`, "use of `fmt.Printf` forbidden by pattern `fmt\\.Printf` at testing.go:5:2") }) + t.Run("it finds forbidden, renamed identifiers", func(t *testing.T) { + linter, _ := NewLinter([]string{`fmt\.Printf`}) + expectIssues(t, linter, ` +package bar + +import renamed "fmt" + +func foo() { + renamed.Printf("here i am") +}` /* only detected inside golanci-lint */) + }) + t.Run("displays custom messages", func(t *testing.T) { linter, _ := NewLinter([]string{`^fmt\.Printf(# a custom message)?$`}) expectIssues(t, linter, ` @@ -134,24 +151,86 @@ func ExampleFoo() { }`) assert.NotEmpty(t, issues) }) + + t.Run("import renames not detected by simple pattern", func(t *testing.T) { + linter, _ := NewLinter([]string{`fmt\.Printf`}, OptionExcludeGodocExamples(false)) + issues := parseFile(t, linter, "file.go", ` +package bar + +import fmt2 "fmt" + +func ExampleFoo() { + fmt2.Printf("here i am") +}`) + assert.Empty(t, issues) + }) + + t.Run("import renames detected by package pattern", func(t *testing.T) { + linter, _ := NewLinter([]string{`(?Pfmt)\.Printf`}, OptionExcludeGodocExamples(false)) + expectIssues(t, linter, ` +package bar + +import fmt2 "fmt" + +func ExampleFoo() { + fmt2.Printf("here i am") +}`, "use of `fmt2.Printf` forbidden by pattern `(?Pfmt)\\.Printf` at testing.go:7:2") + }) + } +// sourcePath matches "at /tmp/TestForbiddenIdentifiersdisplays_custom_messages4260088387/001/testing.go". +var sourcePath = regexp.MustCompile(`at .*/([[:alnum:]]+.go)`) + func expectIssues(t *testing.T, linter *Linter, contents string, issues ...string) { actualIssues := parseFile(t, linter, "testing.go", contents) actualIssueStrs := make([]string, 0, len(actualIssues)) for _, i := range actualIssues { - actualIssueStrs = append(actualIssueStrs, i.String()) + str := i.String() + str = sourcePath.ReplaceAllString(str, "at $1") + actualIssueStrs = append(actualIssueStrs, str) } assert.ElementsMatch(t, issues, actualIssueStrs) } func parseFile(t *testing.T, linter *Linter, fileName, contents string) []Issue { - fset := token.NewFileSet() - expr, err := parser.ParseFile(fset, fileName, contents, parser.ParseComments) + // We can use packages.Load if we put a single file into a separate + // directory and parse it with Go modules of. We have to be in that + // directory to use "." as pattern, parsing it via the absolute path + // from the forbidigo project doesn't work ("cannot import absolute + // path"). + tmpDir := t.TempDir() + if err := os.WriteFile(path.Join(tmpDir, fileName), []byte(contents), 0644); err != nil { + t.Fatalf("could not write source file: %v", err) + } + env := os.Environ() + env = append(env, "GO111MODULE=off") + cfg := packages.Config{ + Mode: packages.NeedSyntax | packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps, + Env: env, + Tests: true, + } + pwd, err := os.Getwd() + require.NoError(t, err) + defer os.Chdir(pwd) + err = os.Chdir(tmpDir) + require.NoError(t, err) + pkgs, err := packages.Load(&cfg, ".") if err != nil { - t.Fatalf("unable to parse file contents: %s", err) + t.Fatalf("could not load packages: %v", err) + } + var issues []Issue + for _, p := range pkgs { + nodes := make([]ast.Node, 0, len(p.Syntax)) + for _, n := range p.Syntax { + nodes = append(nodes, n) + } + newIssues, err := linter.RunWithTypes(p.Fset, p.TypesInfo, nodes...) + if err != nil { + log.Fatalf("failed: %s", err) + } + issues = append(issues, newIssues...) } - issues, err := linter.Run(fset, expr) if err != nil { t.Fatalf("unable to parse file: %s", err) } diff --git a/forbidigo/patterns.go b/forbidigo/patterns.go index 9dc70ec..49c19dc 100644 --- a/forbidigo/patterns.go +++ b/forbidigo/patterns.go @@ -8,8 +8,9 @@ import ( ) type pattern struct { - pattern *regexp.Regexp - msg string + pattern *regexp.Regexp + msg string + matchPackage bool } func parse(ptrn string) (*pattern, error) { @@ -22,7 +23,14 @@ func parse(ptrn string) (*pattern, error) { return nil, fmt.Errorf("unable to parse pattern `%s`: %s", ptrn, err) } msg := extractComment(re) - return &pattern{pattern: ptrnRe, msg: msg}, nil + matchPackage := false + for _, groupName := range ptrnRe.SubexpNames() { + switch groupName { + case "pkg": + matchPackage = true + } + } + return &pattern{pattern: ptrnRe, msg: msg, matchPackage: matchPackage}, nil } // Traverse the leaf submatches in the regex tree and extract a comment, if any diff --git a/forbidigo/patterns_test.go b/forbidigo/patterns_test.go index ce4e617..1b606df 100644 --- a/forbidigo/patterns_test.go +++ b/forbidigo/patterns_test.go @@ -9,9 +9,10 @@ import ( func TestParseValidPatterns(t *testing.T) { for _, tc := range []struct { - name string - ptrn string - expectedComment string + name string + ptrn string + expectedComment string + expectedMatchPackage bool }{ { name: "simple expression, no comment", @@ -41,12 +42,18 @@ func TestParseValidPatterns(t *testing.T) { ptrn: `^fmt\.Println(# Please don't use this!)?$`, expectedComment: "Please don't use this!", }, + { + name: "match package with non-empty group", + ptrn: `^(?Pfmt).Println$`, + expectedMatchPackage: true, + }, } { t.Run(tc.name, func(t *testing.T) { ptrn, err := parse(tc.ptrn) require.Nil(t, err) assert.Equal(t, tc.ptrn, ptrn.pattern.String()) assert.Equal(t, tc.expectedComment, ptrn.msg) + assert.Equal(t, tc.expectedMatchPackage, ptrn.matchPackage, "match pattern") }) } } diff --git a/go.mod b/go.mod index 45bc675..35418cb 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.12 require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.4.0 - golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc + golang.org/x/tools v0.3.0 ) diff --git a/go.sum b/go.sum index 545861c..b2b5936 100644 --- a/go.sum +++ b/go.sum @@ -8,18 +8,45 @@ github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc h1:+GB9/q0gCzmtaIl6WdoJFMS3lPwrR6rpcMyY6jfQHAw= golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/main.go b/main.go index 568940b..d458c9f 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ func main() { } cfg := packages.Config{ - Mode: packages.NeedSyntax | packages.NeedName | packages.NeedFiles | packages.NeedTypes, + Mode: packages.NeedSyntax | packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedTypesInfo, Tests: *includeTests, } pkgs, err := packages.Load(&cfg, flag.Args()[firstPkg:]...) @@ -55,7 +55,7 @@ func main() { for _, n := range p.Syntax { nodes = append(nodes, n) } - newIssues, err := linter.Run(p.Fset, nodes...) + newIssues, err := linter.RunWithTypes(p.Fset, p.TypesInfo, nodes...) if err != nil { log.Fatalf("failed: %s", err) } diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index a14ba18..9c3d21b 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -41,6 +41,7 @@ func NewAnalyzer() *analysis.Analyzer { usePermitDirective: true, includeExamples: true, } + flags.Var(&listVar{values: &a.patterns}, "p", "pattern") flags.BoolVar(&a.includeExamples, "examples", false, "check godoc examples") flags.BoolVar(&a.usePermitDirective, "permit", true, `when set, lines with "//permit" directives will be ignored`) @@ -67,7 +68,7 @@ func (a *analyzer) runAnalysis(pass *analysis.Pass) (interface{}, error) { for _, f := range pass.Files { nodes = append(nodes, f) } - issues, err := linter.Run(pass.Fset, nodes...) + issues, err := linter.RunWithTypes(pass.Fset, pass.TypesInfo, nodes...) if err != nil { return nil, err } diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go index 6d521cc..fc5ba13 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/analyzer/analyzer_test.go @@ -3,12 +3,21 @@ package analyzer_test import ( "testing" + "github.com/ashanbrown/forbidigo/forbidigo" "github.com/ashanbrown/forbidigo/pkg/analyzer" "golang.org/x/tools/go/analysis/analysistest" ) func TestAnalyzer(t *testing.T) { testdata := analysistest.TestData() + patterns := append(forbidigo.DefaultPatterns(), + `^(?Pexample.com/some/pkg)\.Forbidden$`, + ) a := analyzer.NewAnalyzer() + for _, pattern := range patterns { + if err := a.Flags.Set("p", pattern); err != nil { + t.Fatalf("unexpected error when setting pattern: %v", err) + } + } analysistest.Run(t, testdata, a, "") } diff --git a/pkg/analyzer/testdata/forbidden.go b/pkg/analyzer/testdata/forbidden.go index 7966ae7..e8cdcd5 100644 --- a/pkg/analyzer/testdata/forbidden.go +++ b/pkg/analyzer/testdata/forbidden.go @@ -1,10 +1,24 @@ package testdata -import "fmt" +import ( + "fmt" + alias "fmt" + + "example.com/some/pkg" +) func Foo() { fmt.Println("here I am") // want "forbidden by pattern" fmt.Printf("this is ok") //permit:fmt.Printf // this is ok - print("not ok") // want "forbidden by pattern" - println("also not ok") // want "forbidden by pattern" + print("not ok") // no package, not matched + println("also not ok") // no package, not matched + alias.Println("hello") // want "forbidden by pattern" + pkg.Forbidden() // want "pkg.Forbidden.*forbidden by pattern .*example.com/some/pkg.*Forbidden" +} + +func Bar() string { + fmt := struct { + Println string + }{} + return fmt.Println // not the fmt package, not matched } diff --git a/pkg/analyzer/testdata/src/example.com/some/pkg/pkg.go b/pkg/analyzer/testdata/src/example.com/some/pkg/pkg.go new file mode 100644 index 0000000..46872b3 --- /dev/null +++ b/pkg/analyzer/testdata/src/example.com/some/pkg/pkg.go @@ -0,0 +1,4 @@ +package pkg + +func Forbidden() { +}