github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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.Fprint(os.Stderr, usageString)
   171  	fmt.Fprintln(os.Stderr)
   172  	flag.PrintDefaults()
   173  	os.Exit(2)
   174  }
   175  
   176  func report(err error) {
   177  	scanner.PrintError(os.Stderr, err)
   178  	if list, ok := err.(scanner.ErrorList); ok {
   179  		errorCount += len(list)
   180  		return
   181  	}
   182  	errorCount++
   183  }
   184  
   185  // parse may be called concurrently
   186  func parse(filename string, src interface{}) (*ast.File, error) {
   187  	if *verbose {
   188  		fmt.Println(filename)
   189  	}
   190  	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
   191  	if *printAST {
   192  		ast.Print(fset, file)
   193  	}
   194  	return file, err
   195  }
   196  
   197  func parseStdin() (*ast.File, error) {
   198  	src, err := ioutil.ReadAll(os.Stdin)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return parse("<standard input>", src)
   203  }
   204  
   205  func parseFiles(dir string, filenames []string) ([]*ast.File, error) {
   206  	files := make([]*ast.File, len(filenames))
   207  	errors := make([]error, len(filenames))
   208  
   209  	var wg sync.WaitGroup
   210  	for i, filename := range filenames {
   211  		wg.Add(1)
   212  		go func(i int, filepath string) {
   213  			defer wg.Done()
   214  			files[i], errors[i] = parse(filepath, nil)
   215  		}(i, filepath.Join(dir, filename))
   216  		if sequential {
   217  			wg.Wait()
   218  		}
   219  	}
   220  	wg.Wait()
   221  
   222  	// if there are errors, return the first one for deterministic results
   223  	for _, err := range errors {
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  	}
   228  
   229  	return files, nil
   230  }
   231  
   232  func parseDir(dir string) ([]*ast.File, error) {
   233  	ctxt := build.Default
   234  	pkginfo, err := ctxt.ImportDir(dir, 0)
   235  	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
   236  		return nil, err
   237  	}
   238  
   239  	if *xtestFiles {
   240  		return parseFiles(dir, pkginfo.XTestGoFiles)
   241  	}
   242  
   243  	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
   244  	if *testFiles {
   245  		filenames = append(filenames, pkginfo.TestGoFiles...)
   246  	}
   247  	return parseFiles(dir, filenames)
   248  }
   249  
   250  func getPkgFiles(args []string) ([]*ast.File, error) {
   251  	if len(args) == 0 {
   252  		// stdin
   253  		file, err := parseStdin()
   254  		if err != nil {
   255  			return nil, err
   256  		}
   257  		return []*ast.File{file}, nil
   258  	}
   259  
   260  	if len(args) == 1 {
   261  		// possibly a directory
   262  		path := args[0]
   263  		info, err := os.Stat(path)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  		if info.IsDir() {
   268  			return parseDir(path)
   269  		}
   270  	}
   271  
   272  	// list of files
   273  	return parseFiles("", args)
   274  }
   275  
   276  func checkPkgFiles(files []*ast.File) {
   277  	type bailout struct{}
   278  
   279  	// if checkPkgFiles is called multiple times, set up conf only once
   280  	conf := types.Config{
   281  		FakeImportC: true,
   282  		Error: func(err error) {
   283  			if !*allErrors && errorCount >= 10 {
   284  				panic(bailout{})
   285  			}
   286  			report(err)
   287  		},
   288  		Importer: importer.ForCompiler(fset, *compiler, nil),
   289  		Sizes:    SizesFor(build.Default.Compiler, build.Default.GOARCH),
   290  	}
   291  
   292  	defer func() {
   293  		switch p := recover().(type) {
   294  		case nil, bailout:
   295  			// normal return or early exit
   296  		default:
   297  			// re-panic
   298  			panic(p)
   299  		}
   300  	}()
   301  
   302  	const path = "pkg" // any non-empty string will do for now
   303  	conf.Check(path, fset, files, nil)
   304  }
   305  
   306  func printStats(d time.Duration) {
   307  	fileCount := 0
   308  	lineCount := 0
   309  	fset.Iterate(func(f *token.File) bool {
   310  		fileCount++
   311  		lineCount += f.LineCount()
   312  		return true
   313  	})
   314  
   315  	fmt.Printf(
   316  		"%s (%d files, %d lines, %d lines/s)\n",
   317  		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
   318  	)
   319  }
   320  
   321  func main() {
   322  	flag.Usage = usage
   323  	flag.Parse()
   324  	initParserMode()
   325  
   326  	start := time.Now()
   327  
   328  	files, err := getPkgFiles(flag.Args())
   329  	if err != nil {
   330  		report(err)
   331  		os.Exit(2)
   332  	}
   333  
   334  	checkPkgFiles(files)
   335  	if errorCount > 0 {
   336  		os.Exit(2)
   337  	}
   338  
   339  	if *verbose {
   340  		printStats(time.Since(start))
   341  	}
   342  }