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