github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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.Fprint(os.Stderr, usageString) 171 fmt.Fprintln(os.Stderr) 172 flag.PrintDefaults() 173 os.Exit(2) 174 } 175 176 func report(err error) { 177 scanner.PrintError(os.Stderr, err) 178 if list, ok := err.(scanner.ErrorList); ok { 179 errorCount += len(list) 180 return 181 } 182 errorCount++ 183 } 184 185 // parse may be called concurrently 186 func parse(filename string, src interface{}) (*ast.File, error) { 187 if *verbose { 188 fmt.Println(filename) 189 } 190 file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently 191 if *printAST { 192 ast.Print(fset, file) 193 } 194 return file, err 195 } 196 197 func parseStdin() (*ast.File, error) { 198 src, err := ioutil.ReadAll(os.Stdin) 199 if err != nil { 200 return nil, err 201 } 202 return parse("<standard input>", src) 203 } 204 205 func parseFiles(dir string, filenames []string) ([]*ast.File, error) { 206 files := make([]*ast.File, len(filenames)) 207 errors := make([]error, len(filenames)) 208 209 var wg sync.WaitGroup 210 for i, filename := range filenames { 211 wg.Add(1) 212 go func(i int, filepath string) { 213 defer wg.Done() 214 files[i], errors[i] = parse(filepath, nil) 215 }(i, filepath.Join(dir, filename)) 216 if sequential { 217 wg.Wait() 218 } 219 } 220 wg.Wait() 221 222 // if there are errors, return the first one for deterministic results 223 for _, err := range errors { 224 if err != nil { 225 return nil, err 226 } 227 } 228 229 return files, nil 230 } 231 232 func parseDir(dir string) ([]*ast.File, error) { 233 ctxt := build.Default 234 pkginfo, err := ctxt.ImportDir(dir, 0) 235 if _, nogo := err.(*build.NoGoError); err != nil && !nogo { 236 return nil, err 237 } 238 239 if *xtestFiles { 240 return parseFiles(dir, pkginfo.XTestGoFiles) 241 } 242 243 filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...) 244 if *testFiles { 245 filenames = append(filenames, pkginfo.TestGoFiles...) 246 } 247 return parseFiles(dir, filenames) 248 } 249 250 func getPkgFiles(args []string) ([]*ast.File, error) { 251 if len(args) == 0 { 252 // stdin 253 file, err := parseStdin() 254 if err != nil { 255 return nil, err 256 } 257 return []*ast.File{file}, nil 258 } 259 260 if len(args) == 1 { 261 // possibly a directory 262 path := args[0] 263 info, err := os.Stat(path) 264 if err != nil { 265 return nil, err 266 } 267 if info.IsDir() { 268 return parseDir(path) 269 } 270 } 271 272 // list of files 273 return parseFiles("", args) 274 } 275 276 func checkPkgFiles(files []*ast.File) { 277 type bailout struct{} 278 279 // if checkPkgFiles is called multiple times, set up conf only once 280 conf := types.Config{ 281 FakeImportC: true, 282 Error: func(err error) { 283 if !*allErrors && errorCount >= 10 { 284 panic(bailout{}) 285 } 286 report(err) 287 }, 288 Importer: importer.ForCompiler(fset, *compiler, nil), 289 Sizes: SizesFor(build.Default.Compiler, build.Default.GOARCH), 290 } 291 292 defer func() { 293 switch p := recover().(type) { 294 case nil, bailout: 295 // normal return or early exit 296 default: 297 // re-panic 298 panic(p) 299 } 300 }() 301 302 const path = "pkg" // any non-empty string will do for now 303 conf.Check(path, fset, files, nil) 304 } 305 306 func printStats(d time.Duration) { 307 fileCount := 0 308 lineCount := 0 309 fset.Iterate(func(f *token.File) bool { 310 fileCount++ 311 lineCount += f.LineCount() 312 return true 313 }) 314 315 fmt.Printf( 316 "%s (%d files, %d lines, %d lines/s)\n", 317 d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()), 318 ) 319 } 320 321 func main() { 322 flag.Usage = usage 323 flag.Parse() 324 initParserMode() 325 326 start := time.Now() 327 328 files, err := getPkgFiles(flag.Args()) 329 if err != nil { 330 report(err) 331 os.Exit(2) 332 } 333 334 checkPkgFiles(files) 335 if errorCount > 0 { 336 os.Exit(2) 337 } 338 339 if *verbose { 340 printStats(time.Since(start)) 341 } 342 }