Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export Analyzer interface #11

Merged
merged 4 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 1 addition & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
linters:
enable-all: true
linters: {}
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
repos:
- repo: https://github.com/golangci/golangci-lint
rev: v1.17.1
rev: v1.44.2
hooks:
- id: golangci-lint
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: trailing-whitespace
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# makezero

[![CircleCI](https://circleci.com/gh/ashanbrown/makezero/tree/master.svg?style=svg)](https://circleci.com/gh/ashanbrown/makezero/tree/master)

makezero is a Go static analysis tool to find slice declarations that are not initialized with zero length and are later
used with append.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ go 1.12

require (
github.com/stretchr/testify v1.4.0
golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc
golang.org/x/tools v0.1.9
)
31 changes: 22 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,37 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {
}
linter := makezero.NewLinter(*always)

var issues []makezero.Issue // nolint:prealloc // don't know how many there will be
var issues []makezero.Issue
for _, p := range pkgs {
nodes := make([]ast.Node, 0, len(p.Syntax))
for _, n := range p.Syntax {
Expand Down
27 changes: 22 additions & 5 deletions makezero/makezero.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// makezero provides a linter for appends to slices initialized with non-zero length.
// Package makezero provides a linter for appends to slices initialized with non-zero length.
package makezero

import (
Expand All @@ -13,27 +13,33 @@ import (
)

// a decl might include multiple var,
// so var name with decl make final uniq obj
// so var name with decl make final uniq obj.
type uniqDecl struct {
varName string
decl interface{}
}

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

type AppendIssue struct {
name string
pos token.Pos
position token.Position
}

func (a AppendIssue) Details() string {
return fmt.Sprintf("append to slice `%s` with non-zero initialized length", a.name)
}

func (a AppendIssue) Pos() token.Pos {
return a.pos
}

func (a AppendIssue) Position() token.Position {
return a.position
}
Expand All @@ -42,13 +48,18 @@ func (a AppendIssue) String() string { return toString(a) }

type MustHaveNonZeroInitLenIssue struct {
name string
pos token.Pos
position token.Position
}

func (i MustHaveNonZeroInitLenIssue) Details() string {
return fmt.Sprintf("slice `%s` does not have non-zero initial length", i.name)
}

func (i MustHaveNonZeroInitLenIssue) Pos() token.Pos {
return i.pos
}

func (i MustHaveNonZeroInitLenIssue) Position() token.Position {
return i.position
}
Expand Down Expand Up @@ -81,7 +92,7 @@ func NewLinter(initialLengthMustBeZero bool) *Linter {
}

func (l Linter) Run(fset *token.FileSet, info *types.Info, nodes ...ast.Node) ([]Issue, error) {
var issues []Issue // nolint:prealloc // don't know how many there will be
var issues []Issue
for _, node := range nodes {
var comments []*ast.CommentGroup
if file, ok := node.(*ast.File); ok {
Expand Down Expand Up @@ -110,7 +121,12 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor {
if sliceIdent, ok := node.Args[0].(*ast.Ident); ok &&
v.hasNonZeroInitialLength(sliceIdent) &&
!v.hasNoLintOnSameLine(fun) {
v.issues = append(v.issues, AppendIssue{name: sliceIdent.Name, position: v.fset.Position(fun.Pos())})
v.issues = append(v.issues,
AppendIssue{
name: sliceIdent.Name,
pos: fun.Pos(),
position: v.fset.Position(fun.Pos()),
})
}
case *ast.AssignStmt:
for i, right := range node.Rhs {
Expand All @@ -130,6 +146,7 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor {
if v.initLenMustBeZero && !v.hasNoLintOnSameLine(fun) {
v.issues = append(v.issues, MustHaveNonZeroInitLenIssue{
name: v.textFor(left),
pos: node.Pos(),
position: v.fset.Position(node.Pos()),
})
}
Expand Down Expand Up @@ -201,7 +218,7 @@ func (v *visitor) isSlice(node ast.Node) bool {
}

func (v *visitor) hasNoLintOnSameLine(node ast.Node) bool {
var nolint = regexp.MustCompile(`^\s*nozero\b`)
nolint := regexp.MustCompile(`^\s*nozero\b`)
nodePos := v.fset.Position(node.Pos())
for _, c := range v.comments {
commentPos := v.fset.Position(c.Pos())
Expand Down
1 change: 0 additions & 1 deletion makezero/makezero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ func foo() {
append(x, 1) //nozeroxxx
}`, "append to slice `x` with non-zero initialized length at testing.go:7:3")
})

})

t.Run("ignores more complex constructs than basic variables", func(t *testing.T) {
Expand Down
52 changes: 52 additions & 0 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package analyzer

import (
"flag"
"go/ast"

"github.com/ashanbrown/makezero/makezero"
"golang.org/x/tools/go/analysis"
)

type analyzer struct {
always bool
}

// NewAnalyzer returns a go/analysis-compatible analyzer
// Set "-always" to report any non-empty slice initialization.
func NewAnalyzer() *analysis.Analyzer {
var flags flag.FlagSet
a := analyzer{}
flags.BoolVar(&a.always, "always", false, "report any non-empty slice initializations, regardless of intention")
return &analysis.Analyzer{
Name: "makezero",
Doc: "detect unintended non-empty slice initializations",
Run: a.runAnalysis,
Flags: flags,
}
}

func (a *analyzer) runAnalysis(pass *analysis.Pass) (interface{}, error) {
linter := makezero.NewLinter(a.always)
nodes := make([]ast.Node, 0, len(pass.Files))
for _, f := range pass.Files {
nodes = append(nodes, f)
}
issues, err := linter.Run(pass.Fset, pass.TypesInfo, nodes...)
if err != nil {
return nil, err
}
reportIssues(pass, issues)
return nil, nil
}

func reportIssues(pass *analysis.Pass, issues []makezero.Issue) {
for _, i := range issues {
diag := analysis.Diagnostic{
Pos: i.Pos(),
Message: i.Details(),
Category: "restriction",
}
pass.Report(diag)
}
}
24 changes: 24 additions & 0 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package analyzer_test

import (
"testing"

"github.com/ashanbrown/makezero/pkg/analyzer"
"golang.org/x/tools/go/analysis/analysistest"
)

func TestAppend(t *testing.T) {
testdata := analysistest.TestData()
a := analyzer.NewAnalyzer()
analysistest.Run(t, testdata, a, "./append")
}

func TestAlways(t *testing.T) {
testdata := analysistest.TestData()
a := analyzer.NewAnalyzer()
err := a.Flags.Set("always", "true")
if err != nil {
t.Fatalf("expected no error but got %q", err)
}
analysistest.Run(t, testdata, a, "./always")
}
8 changes: 8 additions & 0 deletions pkg/analyzer/testdata/always/append.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package always

import "fmt"

func Foo() {
x := make([]int, 5) // want "slice `x` does not have non-zero initial length"
fmt.Println(x)
}
9 changes: 9 additions & 0 deletions pkg/analyzer/testdata/append/append.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package append

import "fmt"

func Foo() {
x := make([]int, 5)
x = append(x, 1) // want "append to slice `x` with non-zero initialized length"
fmt.Println(x)
}