Source file
src/go/types/gotype.go
1
2
3
4
5
6
7
8
9
81 package main
82
83 import (
84 "flag"
85 "fmt"
86 "go/ast"
87 "go/build"
88 "go/importer"
89 "go/parser"
90 "go/scanner"
91 "go/token"
92 "go/types"
93 "io"
94 "os"
95 "path/filepath"
96 "sync"
97 "time"
98 )
99
100 var (
101
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
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
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)
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
221 var first error
222 for _, err := range errors {
223 if err != nil {
224 first = err
225
226
227
228
229
230
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
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
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
287 return parseFiles("", args)
288 }
289
290 func checkPkgFiles(files []*ast.File) {
291 type bailout struct{}
292
293
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
310 default:
311
312 panic(p)
313 }
314 }()
315
316 const path = "pkg"
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
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 }
357
View as plain text