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