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