summaryrefslogtreecommitdiff
path: root/src/cmd/vet/main.go
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@debian.org>2013-03-04 21:27:36 +0100
committerMichael Stapelberg <michael@stapelberg.de>2013-03-04 21:27:36 +0100
commit04b08da9af0c450d645ab7389d1467308cfc2db8 (patch)
treedb247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/cmd/vet/main.go
parent917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff)
downloadgolang-upstream/1.1_hg20130304.tar.gz
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/cmd/vet/main.go')
-rw-r--r--src/cmd/vet/main.go231
1 files changed, 196 insertions, 35 deletions
diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go
index 625133315..20f6cca1a 100644
--- a/src/cmd/vet/main.go
+++ b/src/cmd/vet/main.go
@@ -11,9 +11,12 @@ import (
"flag"
"fmt"
"go/ast"
+ "go/build"
"go/parser"
+ "go/printer"
"go/token"
- "io"
+ "go/types"
+ "io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -23,6 +26,24 @@ import (
var verbose = flag.Bool("v", false, "verbose")
var exitCode = 0
+// Flags to control which checks to perform. "all" is set to true here, and disabled later if
+// a flag is set explicitly.
+var report = map[string]*bool{
+ "all": flag.Bool("all", true, "check everything; disabled if any explicit check is requested"),
+ "atomic": flag.Bool("atomic", false, "check for common mistaken usages of the sync/atomic package"),
+ "buildtags": flag.Bool("buildtags", false, "check that +build tags are valid"),
+ "composites": flag.Bool("composites", false, "check that composite literals used type-tagged elements"),
+ "methods": flag.Bool("methods", false, "check that canonically named methods are canonically defined"),
+ "printf": flag.Bool("printf", false, "check printf-like invocations"),
+ "structtags": flag.Bool("structtags", false, "check that struct field tags have canonical format"),
+ "rangeloops": flag.Bool("rangeloops", false, "check that range loop variables are used correctly"),
+}
+
+// vet tells whether to report errors for the named check, a flag name.
+func vet(name string) bool {
+ return *report["all"] || *report[name]
+}
+
// setExit sets the value for os.Exit when it is called, later. It
// remembers the highest value.
func setExit(err int) {
@@ -34,6 +55,8 @@ func setExit(err int) {
// Usage is a replacement usage function for the flags package.
func Usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
+ fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
flag.PrintDefaults()
os.Exit(2)
}
@@ -41,7 +64,9 @@ func Usage() {
// File is a wrapper for the state of a file used in the parser.
// The parse tree walkers are all methods of this type.
type File struct {
+ pkg *Package
fset *token.FileSet
+ name string
file *ast.File
b bytes.Buffer // for use by methods
}
@@ -50,6 +75,14 @@ func main() {
flag.Usage = Usage
flag.Parse()
+ // If a check is named explicitly, turn off the 'all' flag.
+ for name, ptr := range report {
+ if name != "all" && *ptr {
+ *report["all"] = false
+ break
+ }
+ }
+
if *printfuncs != "" {
for _, name := range strings.Split(*printfuncs, ",") {
if len(name) == 0 {
@@ -74,41 +107,135 @@ func main() {
}
if flag.NArg() == 0 {
- doFile("stdin", os.Stdin)
- } else {
+ Usage()
+ }
+ dirs := false
+ files := false
+ for _, name := range flag.Args() {
+ // Is it a directory?
+ fi, err := os.Stat(name)
+ if err != nil {
+ warnf("error walking tree: %s", err)
+ continue
+ }
+ if fi.IsDir() {
+ dirs = true
+ } else {
+ files = true
+ }
+ }
+ if dirs && files {
+ Usage()
+ }
+ if dirs {
for _, name := range flag.Args() {
- // Is it a directory?
- if fi, err := os.Stat(name); err == nil && fi.IsDir() {
- walkDir(name)
- } else {
- doFile(name, nil)
- }
+ walkDir(name)
}
+ return
}
+ doPackage(flag.Args())
os.Exit(exitCode)
}
-// doFile analyzes one file. If the reader is nil, the source code is read from the
-// named file.
-func doFile(name string, reader io.Reader) {
- fs := token.NewFileSet()
- parsedFile, err := parser.ParseFile(fs, name, reader, 0)
+// prefixDirectory places the directory name on the beginning of each name in the list.
+func prefixDirectory(directory string, names []string) {
+ if directory != "." {
+ for i, name := range names {
+ names[i] = filepath.Join(directory, name)
+ }
+ }
+}
+
+// doPackageDir analyzes the single package found in the directory, if there is one,
+// plus a test package, if there is one.
+func doPackageDir(directory string) {
+ pkg, err := build.Default.ImportDir(directory, 0)
if err != nil {
- errorf("%s: %s", name, err)
+ // If it's just that there are no go source files, that's fine.
+ if _, nogo := err.(*build.NoGoError); nogo {
+ return
+ }
+ // Non-fatal: we are doing a recursive walk and there may be other directories.
+ warnf("cannot process directory %s: %s", directory, err)
return
}
- file := &File{fset: fs, file: parsedFile}
- file.walkFile(name, parsedFile)
+ var names []string
+ names = append(names, pkg.GoFiles...)
+ names = append(names, pkg.CgoFiles...)
+ names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
+ prefixDirectory(directory, names)
+ doPackage(names)
+ // Is there also a "foo_test" package? If so, do that one as well.
+ if len(pkg.XTestGoFiles) > 0 {
+ names = pkg.XTestGoFiles
+ prefixDirectory(directory, names)
+ doPackage(names)
+ }
+}
+
+type Package struct {
+ types map[ast.Expr]types.Type
+ values map[ast.Expr]interface{}
+}
+
+// doPackage analyzes the single package constructed from the named files.
+func doPackage(names []string) {
+ var files []*File
+ var astFiles []*ast.File
+ fs := token.NewFileSet()
+ for _, name := range names {
+ f, err := os.Open(name)
+ if err != nil {
+ errorf("%s: %s", name, err)
+ }
+ defer f.Close()
+ data, err := ioutil.ReadAll(f)
+ if err != nil {
+ errorf("%s: %s", name, err)
+ }
+ checkBuildTag(name, data)
+ parsedFile, err := parser.ParseFile(fs, name, bytes.NewReader(data), 0)
+ if err != nil {
+ errorf("%s: %s", name, err)
+ }
+ files = append(files, &File{fset: fs, name: name, file: parsedFile})
+ astFiles = append(astFiles, parsedFile)
+ }
+ pkg := new(Package)
+ pkg.types = make(map[ast.Expr]types.Type)
+ pkg.values = make(map[ast.Expr]interface{})
+ exprFn := func(x ast.Expr, typ types.Type, val interface{}) {
+ pkg.types[x] = typ
+ if val != nil {
+ pkg.values[x] = val
+ }
+ }
+ // By providing the Context with our own error function, it will continue
+ // past the first error. There is no need for that function to do anything.
+ context := types.Context{
+ Expr: exprFn,
+ Error: func(error) {},
+ }
+ // Type check the package.
+ _, err := context.Check(fs, astFiles)
+ if err != nil && *verbose {
+ warnf("%s", err)
+ }
+ for _, file := range files {
+ file.pkg = pkg
+ file.walkFile(file.name, file.file)
+ }
}
func visit(path string, f os.FileInfo, err error) error {
if err != nil {
errorf("walk error: %s", err)
- return nil
}
- if !f.IsDir() && strings.HasSuffix(path, ".go") {
- doFile(path, nil)
+ // One package per directory. Ignore the files themselves.
+ if !f.IsDir() {
+ return nil
}
+ doPackageDir(path)
return nil
}
@@ -117,11 +244,18 @@ func walkDir(root string) {
filepath.Walk(root, visit)
}
-// error formats the error to standard error, adding program
-// identification and a newline
+// errorf formats the error to standard error, adding program
+// identification and a newline, and exits.
func errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
- setExit(2)
+ os.Exit(2)
+}
+
+// warnf formats the error to standard error, adding program
+// identification and a newline, but does not exit.
+func warnf(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
+ setExit(1)
}
// Println is fmt.Println guarded by -v.
@@ -152,16 +286,22 @@ func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
setExit(1)
}
+func (f *File) loc(pos token.Pos) string {
+ // Do not print columns. Because the pos often points to the start of an
+ // expression instead of the inner part with the actual error, the
+ // precision can mislead.
+ posn := f.fset.Position(pos)
+ return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line)
+}
+
// Warn reports an error but does not set the exit code.
func (f *File) Warn(pos token.Pos, args ...interface{}) {
- loc := f.fset.Position(pos).String() + ": "
- fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
+ fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...))
}
// Warnf reports a formatted error but does not set the exit code.
func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
- loc := f.fset.Position(pos).String() + ": "
- fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
+ fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...)
}
// walkFile walks the file's tree.
@@ -173,6 +313,8 @@ func (f *File) walkFile(name string, file *ast.File) {
// Visit implements the ast.Visitor interface.
func (f *File) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
+ case *ast.AssignStmt:
+ f.walkAssignStmt(n)
case *ast.CallExpr:
f.walkCallExpr(n)
case *ast.CompositeLit:
@@ -183,15 +325,32 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
f.walkMethodDecl(n)
case *ast.InterfaceType:
f.walkInterfaceType(n)
+ case *ast.RangeStmt:
+ f.walkRangeStmt(n)
}
return f
}
+// walkAssignStmt walks an assignment statement
+func (f *File) walkAssignStmt(stmt *ast.AssignStmt) {
+ f.checkAtomicAssignment(stmt)
+}
+
// walkCall walks a call expression.
func (f *File) walkCall(call *ast.CallExpr, name string) {
f.checkFmtPrintfCall(call, name)
}
+// walkCallExpr walks a call expression.
+func (f *File) walkCallExpr(call *ast.CallExpr) {
+ switch x := call.Fun.(type) {
+ case *ast.Ident:
+ f.walkCall(call, x.Name)
+ case *ast.SelectorExpr:
+ f.walkCall(call, x.Sel.Name)
+ }
+}
+
// walkCompositeLit walks a composite literal.
func (f *File) walkCompositeLit(c *ast.CompositeLit) {
f.checkUntaggedLiteral(c)
@@ -228,12 +387,14 @@ func (f *File) walkInterfaceType(t *ast.InterfaceType) {
}
}
-// walkCallExpr walks a call expression.
-func (f *File) walkCallExpr(call *ast.CallExpr) {
- switch x := call.Fun.(type) {
- case *ast.Ident:
- f.walkCall(call, x.Name)
- case *ast.SelectorExpr:
- f.walkCall(call, x.Sel.Name)
- }
+// walkRangeStmt walks a range statement.
+func (f *File) walkRangeStmt(n *ast.RangeStmt) {
+ checkRangeLoop(f, n)
+}
+
+// gofmt returns a string representation of the expression.
+func (f *File) gofmt(x ast.Expr) string {
+ f.b.Reset()
+ printer.Fprint(&f.b, f.fset, x)
+ return f.b.String()
}