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