From 4ddb62d38048bfb555cc164cad0d3dd102b2e5d5 Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Sun, 20 Feb 2022 11:26:42 -0800 Subject: [PATCH] Add analysis.Analyzer interface (#14) Also update linters --- .circleci/config.yml | 4 +- .golangci.yml | 3 +- .pre-commit-config.yaml | 2 +- forbidigo/forbidigo.go | 11 +++- main.go | 2 +- pkg/analyzer/analyzer.go | 87 ++++++++++++++++++++++++++++++ pkg/analyzer/analyzer_test.go | 14 +++++ pkg/analyzer/testdata/forbidden.go | 10 ++++ 8 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 pkg/analyzer/analyzer.go create mode 100644 pkg/analyzer/analyzer_test.go create mode 100644 pkg/analyzer/testdata/forbidden.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e2a398..49aacbd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,10 +3,10 @@ version: 2 jobs: build: docker: - - image: circleci/golang:1.12 + - image: cimg/go:1.17.7 steps: - checkout - - run: sudo apt-get -y -qq install python python-pip + - run: sudo apt-get update && sudo apt-get -y -qq install python pip - run: pip install pre-commit - run: SKIP=no-commit-to-branch pre-commit run -a - run: go test ./... diff --git a/.golangci.yml b/.golangci.yml index 8ba4236..2d89202 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,2 +1,3 @@ linters: - enable-all: true + enable: + - prealloc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3586c7c..925f136 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/golangci/golangci-lint - rev: v1.17.1 + rev: v1.44.2 hooks: - id: golangci-lint diff --git a/forbidigo/forbidigo.go b/forbidigo/forbidigo.go index 17740fa..9b7d46b 100644 --- a/forbidigo/forbidigo.go +++ b/forbidigo/forbidigo.go @@ -16,6 +16,7 @@ import ( type Issue interface { Details() string + Pos() token.Pos Position() token.Position String() string } @@ -23,6 +24,7 @@ type Issue interface { type UsedIssue struct { identifier string pattern string + pos token.Pos position token.Position customMsg string } @@ -39,6 +41,10 @@ func (a UsedIssue) Position() token.Position { return a.position } +func (a UsedIssue) Pos() token.Pos { + return a.pos +} + func (a UsedIssue) String() string { return toString(a) } func toString(i UsedIssue) string { @@ -96,7 +102,7 @@ type visitor struct { } func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { - var issues []Issue //nolint:prealloc // we don't know how many there will be + var issues []Issue for _, node := range nodes { var comments []*ast.CommentGroup isTestFile := false @@ -167,6 +173,7 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor { v.issues = append(v.issues, UsedIssue{ identifier: v.textFor(node), pattern: p.pattern.String(), + pos: node.Pos(), position: v.fset.Position(node.Pos()), customMsg: p.msg, }) @@ -188,7 +195,7 @@ func (v *visitor) permit(node ast.Node) bool { return false } nodePos := v.fset.Position(node.Pos()) - var nolint = regexp.MustCompile(fmt.Sprintf(`^//\s?permit:%s\b`, regexp.QuoteMeta(v.textFor(node)))) + nolint := regexp.MustCompile(fmt.Sprintf(`^//\s?permit:%s\b`, regexp.QuoteMeta(v.textFor(node)))) for _, c := range v.comments { commentPos := v.fset.Position(c.Pos()) if commentPos.Line == nodePos.Line && len(c.List) > 0 && nolint.MatchString(c.List[0].Text) { diff --git a/main.go b/main.go index dfe025c..9e75564 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func main() { log.Fatalf("Could not create linter: %s", err) } - var issues []forbidigo.Issue //nolint:prealloc // we don't know how many there will be + var issues []forbidigo.Issue for _, p := range pkgs { nodes := make([]ast.Node, 0, len(p.Syntax)) for _, n := range p.Syntax { diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go new file mode 100644 index 0000000..a14ba18 --- /dev/null +++ b/pkg/analyzer/analyzer.go @@ -0,0 +1,87 @@ +package analyzer + +import ( + "flag" + "go/ast" + + "github.com/ashanbrown/forbidigo/forbidigo" + "github.com/pkg/errors" + "golang.org/x/tools/go/analysis" +) + +type listVar struct { + values *[]string +} + +func (v *listVar) Set(value string) error { + *v.values = append(*v.values, value) + if value == "" { + return errors.New("value cannot be empty") + } + return nil +} + +func (v *listVar) String() string { + return "" +} + +type analyzer struct { + patterns []string + usePermitDirective bool + includeExamples bool +} + +// NewAnalyzer returns a go/analysis-compatible analyzer +// The "-p" argument can be used to add a pattern. +// Set "-examples" to analyze godoc examples +// Set "-permit=false" to ignore "//permit:" directives. +func NewAnalyzer() *analysis.Analyzer { + var flags flag.FlagSet + a := 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`) + return &analysis.Analyzer{ + Name: "forbidigo", + Doc: "forbid identifiers", + Run: a.runAnalysis, + Flags: flags, + } +} + +func (a *analyzer) runAnalysis(pass *analysis.Pass) (interface{}, error) { + if a.patterns == nil { + a.patterns = forbidigo.DefaultPatterns() + } + linter, err := forbidigo.NewLinter(a.patterns, + forbidigo.OptionIgnorePermitDirectives(!a.usePermitDirective), + forbidigo.OptionExcludeGodocExamples(!a.includeExamples), + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to configure linter") + } + nodes := make([]ast.Node, 0, len(pass.Files)) + for _, f := range pass.Files { + nodes = append(nodes, f) + } + issues, err := linter.Run(pass.Fset, nodes...) + if err != nil { + return nil, err + } + reportIssues(pass, issues) + return nil, nil +} + +func reportIssues(pass *analysis.Pass, issues []forbidigo.Issue) { + for _, i := range issues { + diag := analysis.Diagnostic{ + Pos: i.Pos(), + Message: i.Details(), + Category: "restriction", + } + pass.Report(diag) + } +} diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go new file mode 100644 index 0000000..6d521cc --- /dev/null +++ b/pkg/analyzer/analyzer_test.go @@ -0,0 +1,14 @@ +package analyzer_test + +import ( + "testing" + + "github.com/ashanbrown/forbidigo/pkg/analyzer" + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestAnalyzer(t *testing.T) { + testdata := analysistest.TestData() + a := analyzer.NewAnalyzer() + analysistest.Run(t, testdata, a, "") +} diff --git a/pkg/analyzer/testdata/forbidden.go b/pkg/analyzer/testdata/forbidden.go new file mode 100644 index 0000000..7966ae7 --- /dev/null +++ b/pkg/analyzer/testdata/forbidden.go @@ -0,0 +1,10 @@ +package testdata + +import "fmt" + +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" +}