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