blob: 856d654abaf86506aa7a950f1c3e9eea4297949f [file] [log] [blame]
// 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 = "<standard input>"
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{}
conf := types.Config{
Error: func(err error) {
if !*allErrors && errorCount >= 10 {
panic(bailout{})
}
report(err)
},
}
defer func() {
switch err := recover().(type) {
case nil, bailout:
default:
panic(err)
}
}()
conf.Check(path, fset, files, nil)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() == 0 {
fset := token.NewFileSet()
processPackage("<stdin>", fset, parseStdin(fset))
} else {
processFiles("<files>", flag.Args(), true)
}
if errorCount > 0 {
os.Exit(2)
}
}