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