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