github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/src/go/types/gotype.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build ignore
     6  
     7  // Build this command explicitly: go build gotype.go
     8  
     9  /*
    10  The gotype command does syntactic and semantic analysis of Go files
    11  and packages like the front-end of a Go compiler. Errors are reported
    12  if the analysis fails; otherwise gotype is quiet (unless -v is set).
    13  
    14  Without a list of paths, gotype reads from standard input, which
    15  must provide a single Go source file defining a complete package.
    16  
    17  If a single path is specified that is a directory, gotype checks
    18  the Go files in that directory; they must all belong to the same
    19  package.
    20  
    21  Otherwise, each path must be the filename of Go file belonging to
    22  the same package.
    23  
    24  Usage:
    25  	gotype [flags] [path...]
    26  
    27  The flags are:
    28  	-a
    29  		use all (incl. _test.go) files when processing a directory
    30  	-e
    31  		report all errors (not just the first 10)
    32  	-v
    33  		verbose mode
    34  	-c
    35  		compiler used to compile packages (gc or gccgo); default: gc
    36  		(gotype based on Go1.5 and up only)
    37  	-gccgo
    38  		use gccimporter instead of gcimporter
    39  		(gotype based on Go1.4 and before only)
    40  
    41  Debugging flags:
    42  	-seq
    43  		parse sequentially, rather than in parallel
    44  	-ast
    45  		print AST (forces -seq)
    46  	-trace
    47  		print parse trace (forces -seq)
    48  	-comments
    49  		parse comments (ignored unless -ast or -trace is provided)
    50  
    51  Examples:
    52  
    53  To check the files a.go, b.go, and c.go:
    54  
    55  	gotype a.go b.go c.go
    56  
    57  To check an entire package in the directory dir and print the processed files:
    58  
    59  	gotype -v dir
    60  
    61  To check an entire package including tests in the local directory:
    62  
    63  	gotype -a .
    64  
    65  To verify the output of a pipe:
    66  
    67  	echo "package foo" | gotype
    68  
    69  */
    70  package main
    71  
    72  import (
    73  	"flag"
    74  	"fmt"
    75  	"go/ast"
    76  	"go/build"
    77  	"go/importer"
    78  	"go/parser"
    79  	"go/scanner"
    80  	"go/token"
    81  	"go/types"
    82  	"io/ioutil"
    83  	"os"
    84  	"path/filepath"
    85  	"time"
    86  )
    87  
    88  var (
    89  	// main operation modes
    90  	allFiles  = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
    91  	allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
    92  	verbose   = flag.Bool("v", false, "verbose mode")
    93  	gccgo     = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
    94  
    95  	// debugging support
    96  	sequential    = flag.Bool("seq", false, "parse sequentially, rather than in parallel")
    97  	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
    98  	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
    99  	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
   100  )
   101  
   102  var (
   103  	fset       = token.NewFileSet()
   104  	errorCount = 0
   105  	parserMode parser.Mode
   106  	sizes      types.Sizes
   107  )
   108  
   109  func initParserMode() {
   110  	if *allErrors {
   111  		parserMode |= parser.AllErrors
   112  	}
   113  	if *printTrace {
   114  		parserMode |= parser.Trace
   115  	}
   116  	if *parseComments && (*printAST || *printTrace) {
   117  		parserMode |= parser.ParseComments
   118  	}
   119  }
   120  
   121  func initSizes() {
   122  	wordSize := 8
   123  	maxAlign := 8
   124  	switch build.Default.GOARCH {
   125  	case "386", "arm":
   126  		wordSize = 4
   127  		maxAlign = 4
   128  		// add more cases as needed
   129  	}
   130  	sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
   131  }
   132  
   133  func usage() {
   134  	fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
   135  	flag.PrintDefaults()
   136  	os.Exit(2)
   137  }
   138  
   139  func report(err error) {
   140  	scanner.PrintError(os.Stderr, err)
   141  	if list, ok := err.(scanner.ErrorList); ok {
   142  		errorCount += len(list)
   143  		return
   144  	}
   145  	errorCount++
   146  }
   147  
   148  // parse may be called concurrently
   149  func parse(filename string, src interface{}) (*ast.File, error) {
   150  	if *verbose {
   151  		fmt.Println(filename)
   152  	}
   153  	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
   154  	if *printAST {
   155  		ast.Print(fset, file)
   156  	}
   157  	return file, err
   158  }
   159  
   160  func parseStdin() (*ast.File, error) {
   161  	src, err := ioutil.ReadAll(os.Stdin)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return parse("<standard input>", src)
   166  }
   167  
   168  func parseFiles(filenames []string) ([]*ast.File, error) {
   169  	files := make([]*ast.File, len(filenames))
   170  
   171  	if *sequential {
   172  		for i, filename := range filenames {
   173  			var err error
   174  			files[i], err = parse(filename, nil)
   175  			if err != nil {
   176  				return nil, err // leave unfinished goroutines hanging
   177  			}
   178  		}
   179  	} else {
   180  		type parseResult struct {
   181  			file *ast.File
   182  			err  error
   183  		}
   184  
   185  		out := make(chan parseResult)
   186  		for _, filename := range filenames {
   187  			go func(filename string) {
   188  				file, err := parse(filename, nil)
   189  				out <- parseResult{file, err}
   190  			}(filename)
   191  		}
   192  
   193  		for i := range filenames {
   194  			res := <-out
   195  			if res.err != nil {
   196  				return nil, res.err // leave unfinished goroutines hanging
   197  			}
   198  			files[i] = res.file
   199  		}
   200  	}
   201  
   202  	return files, nil
   203  }
   204  
   205  func parseDir(dirname string) ([]*ast.File, error) {
   206  	ctxt := build.Default
   207  	pkginfo, err := ctxt.ImportDir(dirname, 0)
   208  	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
   209  		return nil, err
   210  	}
   211  	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
   212  	if *allFiles {
   213  		filenames = append(filenames, pkginfo.TestGoFiles...)
   214  	}
   215  
   216  	// complete file names
   217  	for i, filename := range filenames {
   218  		filenames[i] = filepath.Join(dirname, filename)
   219  	}
   220  
   221  	return parseFiles(filenames)
   222  }
   223  
   224  func getPkgFiles(args []string) ([]*ast.File, error) {
   225  	if len(args) == 0 {
   226  		// stdin
   227  		file, err := parseStdin()
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  		return []*ast.File{file}, nil
   232  	}
   233  
   234  	if len(args) == 1 {
   235  		// possibly a directory
   236  		path := args[0]
   237  		info, err := os.Stat(path)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		if info.IsDir() {
   242  			return parseDir(path)
   243  		}
   244  	}
   245  
   246  	// list of files
   247  	return parseFiles(args)
   248  }
   249  
   250  func checkPkgFiles(files []*ast.File) {
   251  	compiler := "gc"
   252  	if *gccgo {
   253  		compiler = "gccgo"
   254  	}
   255  	type bailout struct{}
   256  	conf := types.Config{
   257  		FakeImportC: true,
   258  		Error: func(err error) {
   259  			if !*allErrors && errorCount >= 10 {
   260  				panic(bailout{})
   261  			}
   262  			report(err)
   263  		},
   264  		Importer: importer.For(compiler, nil),
   265  		Sizes:    sizes,
   266  	}
   267  
   268  	defer func() {
   269  		switch p := recover().(type) {
   270  		case nil, bailout:
   271  			// normal return or early exit
   272  		default:
   273  			// re-panic
   274  			panic(p)
   275  		}
   276  	}()
   277  
   278  	const path = "pkg" // any non-empty string will do for now
   279  	conf.Check(path, fset, files, nil)
   280  }
   281  
   282  func printStats(d time.Duration) {
   283  	fileCount := 0
   284  	lineCount := 0
   285  	fset.Iterate(func(f *token.File) bool {
   286  		fileCount++
   287  		lineCount += f.LineCount()
   288  		return true
   289  	})
   290  
   291  	fmt.Printf(
   292  		"%s (%d files, %d lines, %d lines/s)\n",
   293  		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
   294  	)
   295  }
   296  
   297  func main() {
   298  	flag.Usage = usage
   299  	flag.Parse()
   300  	if *printAST || *printTrace {
   301  		*sequential = true
   302  	}
   303  	initParserMode()
   304  	initSizes()
   305  
   306  	start := time.Now()
   307  
   308  	files, err := getPkgFiles(flag.Args())
   309  	if err != nil {
   310  		report(err)
   311  		os.Exit(2)
   312  	}
   313  
   314  	checkPkgFiles(files)
   315  	if errorCount > 0 {
   316  		os.Exit(2)
   317  	}
   318  
   319  	if *verbose {
   320  		printStats(time.Since(start))
   321  	}
   322  }