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