Skip to content

Commit

Permalink
Add analysis.Analyzer interface (#14)
Browse files Browse the repository at this point in the history
Also update linters
  • Loading branch information
ashanbrown authored Feb 20, 2022
1 parent d31d534 commit 4ddb62d
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 7 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
Expand Down
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
linters:
enable-all: true
enable:
- prealloc
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/golangci/golangci-lint
rev: v1.17.1
rev: v1.44.2
hooks:
- id: golangci-lint
11 changes: 9 additions & 2 deletions forbidigo/forbidigo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import (

type Issue interface {
Details() string
Pos() token.Pos
Position() token.Position
String() string
}

type UsedIssue struct {
identifier string
pattern string
pos token.Pos
position token.Position
customMsg string
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
})
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
87 changes: 87 additions & 0 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -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:<identifier>" 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)
}
}
14 changes: 14 additions & 0 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -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, "")
}
10 changes: 10 additions & 0 deletions pkg/analyzer/testdata/forbidden.go
Original file line number Diff line number Diff line change
@@ -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"
}

0 comments on commit 4ddb62d

Please sign in to comment.