| // 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" | |
| "go/types" | |
| "io/ioutil" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| ) | |
| 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") | |
| // debugging support | |
| printTrace = flag.Bool("trace", false, "print parse trace") | |
| printAST = flag.Bool("ast", false, "print AST") | |
| ) | |
| var exitCode = 0 | |
| func usage() { | |
| fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n") | |
| flag.PrintDefaults() | |
| os.Exit(2) | |
| } | |
| func report(err os.Error) { | |
| scanner.PrintError(os.Stderr, err) | |
| exitCode = 2 | |
| } | |
| // 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 *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 map[string]*ast.File) { | |
| files = make(map[string]*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[filename] = file | |
| } | |
| return | |
| } | |
| func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.File) { | |
| files = make(map[string]*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 { | |
| if files[filename] != nil { | |
| report(os.NewError(fmt.Sprintf("%q: duplicate file", filename))) | |
| continue | |
| } | |
| files[filename] = 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(filenames, false) | |
| } | |
| func processFiles(filenames []string, allFiles bool) { | |
| i := 0 | |
| for _, filename := range filenames { | |
| switch info, err := os.Stat(filename); { | |
| case err != nil: | |
| report(err) | |
| case info.IsRegular(): | |
| if allFiles || isGoFilename(info.Name) { | |
| filenames[i] = filename | |
| i++ | |
| } | |
| case info.IsDirectory(): | |
| if allFiles || *recursive { | |
| processDirectory(filename) | |
| } | |
| } | |
| } | |
| fset := token.NewFileSet() | |
| processPackage(fset, parseFiles(fset, filenames[0:i])) | |
| } | |
| func processPackage(fset *token.FileSet, files map[string]*ast.File) { | |
| // make a package (resolve all identifiers) | |
| pkg, err := ast.NewPackage(fset, files, types.GcImporter, types.Universe) | |
| if err != nil { | |
| report(err) | |
| return | |
| } | |
| _, err = types.Check(fset, pkg) | |
| if err != nil { | |
| report(err) | |
| } | |
| } | |
| func main() { | |
| flag.Usage = usage | |
| flag.Parse() | |
| if flag.NArg() == 0 { | |
| fset := token.NewFileSet() | |
| processPackage(fset, parseStdin(fset)) | |
| } else { | |
| processFiles(flag.Args(), true) | |
| } | |
| os.Exit(exitCode) | |
| } |