// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "flag" "fmt" "go/ast" "go/parser" "go/scanner" "go/token" "io/ioutil" "os" "path/filepath" "strings" "code.google.com/p/go.tools/go/types" ) var ( // main operation modes pkgName = flag.String("p", "", "process only those files in package pkgName") recursive = flag.Bool("r", false, "recursively process subdirectories") verbose = flag.Bool("v", false, "verbose mode") allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)") // debugging support parseComments = flag.Bool("comments", false, "parse comments (ignored if -ast not set)") printTrace = flag.Bool("trace", false, "print parse trace") printAST = flag.Bool("ast", false, "print AST") ) var errorCount int func usage() { fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n") flag.PrintDefaults() os.Exit(2) } func report(err error) { scanner.PrintError(os.Stderr, err) if list, ok := err.(scanner.ErrorList); ok { errorCount += len(list) return } errorCount++ } // parse returns the AST for the Go source src. // The filename is for error reporting only. // The result is nil if there were errors or if // the file does not belong to the -p package. func parse(fset *token.FileSet, filename string, src []byte) *ast.File { if *verbose { fmt.Println(filename) } // ignore files with different package name if *pkgName != "" { file, err := parser.ParseFile(fset, filename, src, parser.PackageClauseOnly) if err != nil { report(err) return nil } if file.Name.Name != *pkgName { if *verbose { fmt.Printf("\tignored (package %s)\n", file.Name.Name) } return nil } } // parse entire file mode := parser.DeclarationErrors if *allErrors { mode |= parser.AllErrors } if *parseComments && *printAST { mode |= parser.ParseComments } if *printTrace { mode |= parser.Trace } file, err := parser.ParseFile(fset, filename, src, mode) if err != nil { report(err) return nil } if *printAST { ast.Print(fset, file) } return file } func parseStdin(fset *token.FileSet) (files []*ast.File) { src, err := ioutil.ReadAll(os.Stdin) if err != nil { report(err) return } const filename = "" if file := parse(fset, filename, src); file != nil { files = []*ast.File{file} } return } func parseFiles(fset *token.FileSet, filenames []string) (files []*ast.File) { for _, filename := range filenames { src, err := ioutil.ReadFile(filename) if err != nil { report(err) continue } if file := parse(fset, filename, src); file != nil { files = append(files, file) } } return } func isGoFilename(filename string) bool { // ignore non-Go files return !strings.HasPrefix(filename, ".") && strings.HasSuffix(filename, ".go") } func processDirectory(dirname string) { f, err := os.Open(dirname) if err != nil { report(err) return } filenames, err := f.Readdirnames(-1) f.Close() if err != nil { report(err) // continue since filenames may not be empty } for i, filename := range filenames { filenames[i] = filepath.Join(dirname, filename) } processFiles(dirname, filenames, false) } func processFiles(path string, filenames []string, allFiles bool) { i := 0 for _, filename := range filenames { switch info, err := os.Stat(filename); { case err != nil: report(err) case info.IsDir(): if allFiles || *recursive { processDirectory(filename) } default: if allFiles || isGoFilename(info.Name()) { filenames[i] = filename i++ } } } fset := token.NewFileSet() processPackage(path, fset, parseFiles(fset, filenames[0:i])) } func processPackage(path string, fset *token.FileSet, files []*ast.File) { type bailout struct{} ctxt := types.Context{ Error: func(err error) { if !*allErrors && errorCount >= 10 { panic(bailout{}) } report(err) }, } defer func() { switch err := recover().(type) { case nil, bailout: default: panic(err) } }() ctxt.Check(path, fset, files...) } func main() { flag.Usage = usage flag.Parse() if flag.NArg() == 0 { fset := token.NewFileSet() processPackage("", fset, parseStdin(fset)) } else { processFiles("", flag.Args(), true) } if errorCount > 0 { os.Exit(2) } }