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