...

Source file src/go/types/gotype.go

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

View as plain text