Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/types/gotype.go

Documentation: go/types

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

View as plain text