github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/go/types/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 // +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 }