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