Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
opennota committed Jul 18, 2014
0 parents commit 54dcd80
Show file tree
Hide file tree
Showing 7 changed files with 1,115 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language: go

go:
- 1.1
- 1.2
- 1.3
- tip

install:
- go get github.com/opennota/check
- go build .

script:
- go test -v

674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
check [![Build Status](https://travis-ci.org/opennota/check.png?branch=master)](https://travis-ci.org/opennota/check)
=======

A set of utilities for checking Go sources.

## Installation

$ go get github.com/opennota/check/cmd/defercheck
$ go get github.com/opennota/check/cmd/structcheck
$ go get github.com/opennota/check/cmd/varcheck

## Usage

Find repeating `defer`s.

```
$ defercheck go/parser
Repeating defer p.closeScope() inside function parseSwitchStmt
```

Find unused struct fields.

```
$ structcheck --help
Usage of structcheck:
-a=false: Count assignments only
-n=1: Minimum use count
$ structcheck fmt
pp.n
ssave.argLimit
ssave.limit
ssave.maxWid
ssave.nlIsEnd
ssave.nlIsSpace
```

Find unused global variables and constants.

```
$ varcheck --help
Usage of varcheck:
-e=false: Report exported variables and constants
$ varcheck image/jpeg
unzig
quantIndexLuminance
quantIndexChrominance
huffIndexChrominanceDC
dcTable
maxV
maxH
acTable
huffIndexLuminanceAC
huffIndexChrominanceAC
huffIndexLuminanceDC
```

## Known limitations

structcheck doesn't handle embedded structs yet.

## License

GNU LGPL v3+

63 changes: 63 additions & 0 deletions cmd/defercheck/defercheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"flag"
"fmt"
"go/ast"
"os"

"github.com/opennota/check"
)

type visitor struct {
m map[*ast.Object][]string
funcName string
}

var exitStatus int

func (v *visitor) Visit(node ast.Node) ast.Visitor {
switch node := node.(type) {
case *ast.FuncDecl:
v.funcName = node.Name.Name
v.m = make(map[*ast.Object][]string)

case *ast.DeferStmt:
if sel, ok := node.Call.Fun.(*ast.SelectorExpr); ok {
if ident, ok := sel.X.(*ast.Ident); ok {
if selectors, ok := v.m[ident.Obj]; !ok {
v.m[ident.Obj] = []string{sel.Sel.Name}
} else {
found := false
for _, selname := range selectors {
if selname == sel.Sel.Name {
fmt.Printf("Repeating defer %s.%s() inside function %s\n",
ident.Name, selname, v.funcName)
found = true
exitStatus = 1
break
}
}
if !found {
v.m[ident.Obj] = append(selectors, sel.Sel.Name)
}
}
}
}
}
return v
}

func main() {
flag.Parse()
pkgPath := "."
if len(flag.Args()) > 0 {
pkgPath = flag.Arg(0)
}
visitor := &visitor{}
_, astFiles := check.ASTFilesForPackage(pkgPath)
for _, f := range astFiles {
ast.Walk(visitor, f)
}
os.Exit(exitStatus)
}
158 changes: 158 additions & 0 deletions cmd/structcheck/structcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"flag"
"fmt"
"go/ast"
"log"
"os"

_ "code.google.com/p/go.tools/go/gcimporter"
"code.google.com/p/go.tools/go/types"
"github.com/opennota/check"
)

var (
assignmentsOnly = flag.Bool("a", false, "Count assignments only")
minimumUseCount = flag.Int("n", 1, "Minimum use count")
)

type visitor struct {
pkg *types.Package
info types.Info
m map[string]map[string]int
}

func (v *visitor) decl(structName, fieldName string) {
if _, ok := v.m[structName]; !ok {
v.m[structName] = make(map[string]int)
}
if _, ok := v.m[structName][fieldName]; !ok {
v.m[structName][fieldName] = 0
}
}

func (v *visitor) assignment(structName, fieldName string) {
if _, ok := v.m[structName]; !ok {
v.m[structName] = make(map[string]int)
}
if _, ok := v.m[structName][fieldName]; ok {
v.m[structName][fieldName]++
} else {
v.m[structName][fieldName] = 1
}
}

func (v *visitor) typeSpec(node *ast.TypeSpec) {
if strukt, ok := node.Type.(*ast.StructType); ok {
structName := node.Name.Name
for _, f := range strukt.Fields.List {
if len(f.Names) > 0 {
fieldName := f.Names[0].Name
v.decl(structName, fieldName)
}
}
}
}

func (v *visitor) names(expr *ast.SelectorExpr) (string, string, bool) {
selection := v.info.Selections[expr]
if selection == nil {
return "", "", false
}
recv := selection.Recv()
if ptr, ok := recv.(*types.Pointer); ok {
recv = ptr.Elem()
}
return types.TypeString(v.pkg, recv), selection.Obj().Name(), true
}

func (v *visitor) assignStmt(node *ast.AssignStmt) {
for _, lhs := range node.Lhs {
var selector *ast.SelectorExpr
switch expr := lhs.(type) {
case *ast.SelectorExpr:
selector = expr
case *ast.IndexExpr:
if expr, ok := expr.X.(*ast.SelectorExpr); ok {
selector = expr
}
}
if selector != nil {
if sn, fn, ok := v.names(selector); ok {
v.assignment(sn, fn)
}
}
}
}

func (v *visitor) compositeLiteral(node *ast.CompositeLit) {
t := v.info.Types[node.Type]
for _, expr := range node.Elts {
if kv, ok := expr.(*ast.KeyValueExpr); ok { // no support for positional field values yet
if ident, ok := kv.Key.(*ast.Ident); ok {
v.assignment(types.TypeString(v.pkg, t.Type), ident.Name)
}
}
}
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
switch node := node.(type) {
case *ast.TypeSpec:
v.typeSpec(node)

case *ast.AssignStmt:
if *assignmentsOnly {
v.assignStmt(node)
}

case *ast.SelectorExpr:
if !*assignmentsOnly {
if sn, fn, ok := v.names(node); ok {
v.assignment(sn, fn)
}
}

case *ast.CompositeLit:
v.compositeLiteral(node)
}

return v
}

func main() {
flag.Parse()
pkgPath := "."
if len(flag.Args()) > 0 {
pkgPath = flag.Arg(0)
}
visitor := &visitor{
info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},

m: make(map[string]map[string]int),
}
fset, astFiles := check.ASTFilesForPackage(pkgPath)
config := types.Config{}
var err error
visitor.pkg, err = config.Check(pkgPath, fset, astFiles, &visitor.info)
if err != nil {
log.Fatal(err)
}
for _, f := range astFiles {
ast.Walk(visitor, f)
}
exitStatus := 0
for structName := range visitor.m {
for fieldName, v := range visitor.m[structName] {
if v < *minimumUseCount {
fmt.Printf("%s.%s\n", structName, fieldName)
exitStatus = 1
}
}
}
os.Exit(exitStatus)
}
85 changes: 85 additions & 0 deletions cmd/varcheck/varcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"flag"
"fmt"
"go/ast"
"os"

"github.com/opennota/check"
)

var (
reportExported = flag.Bool("e", false, "Report exported variables and constants")
)

type visitor struct {
m map[*ast.Object]int
insideFunc bool
}

func (v *visitor) decl(obj *ast.Object) {
if _, ok := v.m[obj]; !ok {
v.m[obj] = 0
}
}

func (v *visitor) use(obj *ast.Object) {
if _, ok := v.m[obj]; ok {
v.m[obj]++
} else {
v.m[obj] = 1
}
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
switch node := node.(type) {
case *ast.Ident:
v.use(node.Obj)

case *ast.ValueSpec:
if !v.insideFunc {
for _, ident := range node.Names {
if ident.Name != "_" {
v.decl(ident.Obj)
}
}
}
for _, val := range node.Values {
ast.Walk(v, val)
}
return nil

case *ast.FuncDecl:
if node.Body != nil {
v.insideFunc = true
ast.Walk(v, node.Body)
v.insideFunc = false
}

return nil
}

return v
}

func main() {
flag.Parse()
pkgPath := "."
if len(flag.Args()) > 0 {
pkgPath = flag.Arg(0)
}
visitor := &visitor{m: make(map[*ast.Object]int)}
_, astFiles := check.ASTFilesForPackage(pkgPath)
for _, f := range astFiles {
ast.Walk(visitor, f)
}
exitStatus := 0
for obj, useCount := range visitor.m {
if useCount == 0 && (*reportExported || !check.IsExported(obj.Name)) {
fmt.Println(obj.Name)
exitStatus = 1
}
}
os.Exit(exitStatus)
}
Loading

0 comments on commit 54dcd80

Please sign in to comment.