github.com/april1989/origin-go-tools@v0.0.32/cmd/gotype/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  // gotype.go is a copy of the original source maintained
     6  // in $GOROOT/src/go/types/gotype.go, but with the call
     7  // to types.SizesFor factored out so we can provide a local
     8  // implementation when compiling against Go 1.8 and earlier.
     9  //
    10  // This code is here for the sole purpose of satisfying historic
    11  // references to this location, and for making gotype accessible
    12  // via 'go get'.
    13  //
    14  // Do NOT make changes to this version as they will not be maintained
    15  // (and possibly overwritten). Any changes should be made to the original
    16  // and then ported to here.
    17  
    18  /*
    19  The gotype command, like the front-end of a Go compiler, parses and
    20  type-checks a single Go package. Errors are reported if the analysis
    21  fails; otherwise gotype is quiet (unless -v is set).
    22  
    23  Without a list of paths, gotype reads from standard input, which
    24  must provide a single Go source file defining a complete package.
    25  
    26  With a single directory argument, gotype checks the Go files in
    27  that directory, comprising a single package. Use -t to include the
    28  (in-package) _test.go files. Use -x to type check only external
    29  test files.
    30  
    31  Otherwise, each path must be the filename of a Go file belonging
    32  to the same package.
    33  
    34  Imports are processed by importing directly from the source of
    35  imported packages (default), or by importing from compiled and
    36  installed packages (by setting -c to the respective compiler).
    37  
    38  The -c flag must be set to a compiler ("gc", "gccgo") when type-
    39  checking packages containing imports with relative import paths
    40  (import "./mypkg") because the source importer cannot know which
    41  files to include for such packages.
    42  
    43  Usage:
    44  	gotype [flags] [path...]
    45  
    46  The flags are:
    47  	-t
    48  		include local test files in a directory (ignored if -x is provided)
    49  	-x
    50  		consider only external test files in a directory
    51  	-e
    52  		report all errors (not just the first 10)
    53  	-v
    54  		verbose mode
    55  	-c
    56  		compiler used for installed packages (gc, gccgo, or source); default: source
    57  
    58  Flags controlling additional output:
    59  	-ast
    60  		print AST (forces -seq)
    61  	-trace
    62  		print parse trace (forces -seq)
    63  	-comments
    64  		parse comments (ignored unless -ast or -trace is provided)
    65  
    66  Examples:
    67  
    68  To check the files a.go, b.go, and c.go:
    69  
    70  	gotype a.go b.go c.go
    71  
    72  To check an entire package including (in-package) tests in the directory dir and print the processed files:
    73  
    74  	gotype -t -v dir
    75  
    76  To check the external test package (if any) in the current directory, based on installed packages compiled with
    77  cmd/compile:
    78  
    79  	gotype -c=gc -x .
    80  
    81  To verify the output of a pipe:
    82  
    83  	echo "package foo" | gotype
    84  
    85  */
    86  package main
    87  
    88  import (
    89  	"flag"
    90  	"fmt"
    91  	"go/ast"
    92  	"go/build"
    93  	"go/importer"
    94  	"go/parser"
    95  	"go/scanner"
    96  	"go/token"
    97  	"go/types"
    98  	"io/ioutil"
    99  	"os"
   100  	"path/filepath"
   101  	"sync"
   102  	"time"
   103  )
   104  
   105  var (
   106  	// main operation modes
   107  	testFiles  = flag.Bool("t", false, "include in-package test files in a directory")
   108  	xtestFiles = flag.Bool("x", false, "consider only external test files in a directory")
   109  	allErrors  = flag.Bool("e", false, "report all errors, not just the first 10")
   110  	verbose    = flag.Bool("v", false, "verbose mode")
   111  	compiler   = flag.String("c", defaultCompiler, "compiler used for installed packages (gc, gccgo, or source)")
   112  
   113  	// additional output control
   114  	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
   115  	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
   116  	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
   117  )
   118  
   119  var (
   120  	fset       = token.NewFileSet()
   121  	errorCount = 0
   122  	sequential = false
   123  	parserMode parser.Mode
   124  )
   125  
   126  func initParserMode() {
   127  	if *allErrors {
   128  		parserMode |= parser.AllErrors
   129  	}
   130  	if *printAST {
   131  		sequential = true
   132  	}
   133  	if *printTrace {
   134  		parserMode |= parser.Trace
   135  		sequential = true
   136  	}
   137  	if *parseComments && (*printAST || *printTrace) {
   138  		parserMode |= parser.ParseComments
   139  	}
   140  }
   141  
   142  const usageString = `usage: gotype [flags] [path ...]
   143  
   144  The gotype command, like the front-end of a Go compiler, parses and
   145  type-checks a single Go package. Errors are reported if the analysis
   146  fails; otherwise gotype is quiet (unless -v is set).
   147  
   148  Without a list of paths, gotype reads from standard input, which
   149  must provide a single Go source file defining a complete package.
   150  
   151  With a single directory argument, gotype checks the Go files in
   152  that directory, comprising a single package. Use -t to include the
   153  (in-package) _test.go files. Use -x to type check only external
   154  test files.
   155  
   156  Otherwise, each path must be the filename of a Go file belonging
   157  to the same package.
   158  
   159  Imports are processed by importing directly from the source of
   160  imported packages (default), or by importing from compiled and
   161  installed packages (by setting -c to the respective compiler).
   162  
   163  The -c flag must be set to a compiler ("gc", "gccgo") when type-
   164  checking packages containing imports with relative import paths
   165  (import "./mypkg") because the source importer cannot know which
   166  files to include for such packages.
   167  `
   168  
   169  func usage() {
   170  	fmt.Fprintln(os.Stderr, usageString)
   171  	flag.PrintDefaults()
   172  	os.Exit(2)
   173  }
   174  
   175  func report(err error) {
   176  	scanner.PrintError(os.Stderr, err)
   177  	if list, ok := err.(scanner.ErrorList); ok {
   178  		errorCount += len(list)
   179  		return
   180  	}
   181  	errorCount++
   182  }
   183  
   184  // parse may be called concurrently
   185  func parse(filename string, src interface{}) (*ast.File, error) {
   186  	if *verbose {
   187  		fmt.Println(filename)
   188  	}
   189  	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
   190  	if *printAST {
   191  		ast.Print(fset, file)
   192  	}
   193  	return file, err
   194  }
   195  
   196  func parseStdin() (*ast.File, error) {
   197  	src, err := ioutil.ReadAll(os.Stdin)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	return parse("<standard input>", src)
   202  }
   203  
   204  func parseFiles(dir string, filenames []string) ([]*ast.File, error) {
   205  	files := make([]*ast.File, len(filenames))
   206  	errors := make([]error, len(filenames))
   207  
   208  	var wg sync.WaitGroup
   209  	for i, filename := range filenames {
   210  		wg.Add(1)
   211  		go func(i int, filepath string) {
   212  			defer wg.Done()
   213  			files[i], errors[i] = parse(filepath, nil)
   214  		}(i, filepath.Join(dir, filename))
   215  		if sequential {
   216  			wg.Wait()
   217  		}
   218  	}
   219  	wg.Wait()
   220  
   221  	// if there are errors, return the first one for deterministic results
   222  	for _, err := range errors {
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  	}
   227  
   228  	return files, nil
   229  }
   230  
   231  func parseDir(dir string) ([]*ast.File, error) {
   232  	ctxt := build.Default
   233  	pkginfo, err := ctxt.ImportDir(dir, 0)
   234  	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
   235  		return nil, err
   236  	}
   237  
   238  	if *xtestFiles {
   239  		return parseFiles(dir, pkginfo.XTestGoFiles)
   240  	}
   241  
   242  	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
   243  	if *testFiles {
   244  		filenames = append(filenames, pkginfo.TestGoFiles...)
   245  	}
   246  	return parseFiles(dir, filenames)
   247  }
   248  
   249  func getPkgFiles(args []string) ([]*ast.File, error) {
   250  	if len(args) == 0 {
   251  		// stdin
   252  		file, err := parseStdin()
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  		return []*ast.File{file}, nil
   257  	}
   258  
   259  	if len(args) == 1 {
   260  		// possibly a directory
   261  		path := args[0]
   262  		info, err := os.Stat(path)
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  		if info.IsDir() {
   267  			return parseDir(path)
   268  		}
   269  	}
   270  
   271  	// list of files
   272  	return parseFiles("", args)
   273  }
   274  
   275  func checkPkgFiles(files []*ast.File) {
   276  	type bailout struct{}
   277  
   278  	// if checkPkgFiles is called multiple times, set up conf only once
   279  	conf := types.Config{
   280  		FakeImportC: true,
   281  		Error: func(err error) {
   282  			if !*allErrors && errorCount >= 10 {
   283  				panic(bailout{})
   284  			}
   285  			report(err)
   286  		},
   287  		Importer: importer.ForCompiler(fset, *compiler, nil),
   288  		Sizes:    SizesFor(build.Default.Compiler, build.Default.GOARCH),
   289  	}
   290  
   291  	defer func() {
   292  		switch p := recover().(type) {
   293  		case nil, bailout:
   294  			// normal return or early exit
   295  		default:
   296  			// re-panic
   297  			panic(p)
   298  		}
   299  	}()
   300  
   301  	const path = "pkg" // any non-empty string will do for now
   302  	conf.Check(path, fset, files, nil)
   303  }
   304  
   305  func printStats(d time.Duration) {
   306  	fileCount := 0
   307  	lineCount := 0
   308  	fset.Iterate(func(f *token.File) bool {
   309  		fileCount++
   310  		lineCount += f.LineCount()
   311  		return true
   312  	})
   313  
   314  	fmt.Printf(
   315  		"%s (%d files, %d lines, %d lines/s)\n",
   316  		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
   317  	)
   318  }
   319  
   320  func main() {
   321  	flag.Usage = usage
   322  	flag.Parse()
   323  	initParserMode()
   324  
   325  	start := time.Now()
   326  
   327  	files, err := getPkgFiles(flag.Args())
   328  	if err != nil {
   329  		report(err)
   330  		os.Exit(2)
   331  	}
   332  
   333  	checkPkgFiles(files)
   334  	if errorCount > 0 {
   335  		os.Exit(2)
   336  	}
   337  
   338  	if *verbose {
   339  		printStats(time.Since(start))
   340  	}
   341  }