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