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