1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build ignore
6
7// Build this command explicitly: go build gotype.go
8
9/*
10The gotype command does syntactic and semantic analysis of Go files
11and packages like the front-end of a Go compiler. Errors are reported
12if the analysis fails; otherwise gotype is quiet (unless -v is set).
13
14Without a list of paths, gotype reads from standard input, which
15must provide a single Go source file defining a complete package.
16
17If a single path is specified that is a directory, gotype checks
18the Go files in that directory; they must all belong to the same
19package.
20
21Otherwise, each path must be the filename of Go file belonging to
22the same package.
23
24Usage:
25	gotype [flags] [path...]
26
27The flags are:
28	-a
29		use all (incl. _test.go) files when processing a directory
30	-e
31		report all errors (not just the first 10)
32	-v
33		verbose mode
34	-c
35		compiler used to compile packages (gc or gccgo); default: gc
36		(gotype based on Go1.5 and up only)
37	-gccgo
38		use gccimporter instead of gcimporter
39		(gotype based on Go1.4 and before only)
40
41Debugging flags:
42	-seq
43		parse sequentially, rather than in parallel
44	-ast
45		print AST (forces -seq)
46	-trace
47		print parse trace (forces -seq)
48	-comments
49		parse comments (ignored unless -ast or -trace is provided)
50
51Examples:
52
53To check the files a.go, b.go, and c.go:
54
55	gotype a.go b.go c.go
56
57To check an entire package in the directory dir and print the processed files:
58
59	gotype -v dir
60
61To check an entire package including tests in the local directory:
62
63	gotype -a .
64
65To verify the output of a pipe:
66
67	echo "package foo" | gotype
68
69*/
70package main
71
72import (
73	"flag"
74	"fmt"
75	"go/ast"
76	"go/build"
77	"go/importer"
78	"go/parser"
79	"go/scanner"
80	"go/token"
81	"go/types"
82	"io/ioutil"
83	"os"
84	"path/filepath"
85	"time"
86)
87
88var (
89	// main operation modes
90	allFiles  = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
91	allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
92	verbose   = flag.Bool("v", false, "verbose mode")
93	gccgo     = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
94
95	// debugging support
96	sequential    = flag.Bool("seq", false, "parse sequentially, rather than in parallel")
97	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
98	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
99	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
100)
101
102var (
103	fset       = token.NewFileSet()
104	errorCount = 0
105	parserMode parser.Mode
106	sizes      types.Sizes
107)
108
109func initParserMode() {
110	if *allErrors {
111		parserMode |= parser.AllErrors
112	}
113	if *printTrace {
114		parserMode |= parser.Trace
115	}
116	if *parseComments && (*printAST || *printTrace) {
117		parserMode |= parser.ParseComments
118	}
119}
120
121func initSizes() {
122	wordSize := 8
123	maxAlign := 8
124	switch build.Default.GOARCH {
125	case "386", "arm":
126		wordSize = 4
127		maxAlign = 4
128		// add more cases as needed
129	}
130	sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
131}
132
133func usage() {
134	fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
135	flag.PrintDefaults()
136	os.Exit(2)
137}
138
139func report(err error) {
140	scanner.PrintError(os.Stderr, err)
141	if list, ok := err.(scanner.ErrorList); ok {
142		errorCount += len(list)
143		return
144	}
145	errorCount++
146}
147
148// parse may be called concurrently
149func parse(filename string, src interface{}) (*ast.File, error) {
150	if *verbose {
151		fmt.Println(filename)
152	}
153	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
154	if *printAST {
155		ast.Print(fset, file)
156	}
157	return file, err
158}
159
160func parseStdin() (*ast.File, error) {
161	src, err := ioutil.ReadAll(os.Stdin)
162	if err != nil {
163		return nil, err
164	}
165	return parse("<standard input>", src)
166}
167
168func parseFiles(filenames []string) ([]*ast.File, error) {
169	files := make([]*ast.File, len(filenames))
170
171	if *sequential {
172		for i, filename := range filenames {
173			var err error
174			files[i], err = parse(filename, nil)
175			if err != nil {
176				return nil, err // leave unfinished goroutines hanging
177			}
178		}
179	} else {
180		type parseResult struct {
181			file *ast.File
182			err  error
183		}
184
185		out := make(chan parseResult)
186		for _, filename := range filenames {
187			go func(filename string) {
188				file, err := parse(filename, nil)
189				out <- parseResult{file, err}
190			}(filename)
191		}
192
193		for i := range filenames {
194			res := <-out
195			if res.err != nil {
196				return nil, res.err // leave unfinished goroutines hanging
197			}
198			files[i] = res.file
199		}
200	}
201
202	return files, nil
203}
204
205func parseDir(dirname string) ([]*ast.File, error) {
206	ctxt := build.Default
207	pkginfo, err := ctxt.ImportDir(dirname, 0)
208	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
209		return nil, err
210	}
211	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
212	if *allFiles {
213		filenames = append(filenames, pkginfo.TestGoFiles...)
214	}
215
216	// complete file names
217	for i, filename := range filenames {
218		filenames[i] = filepath.Join(dirname, filename)
219	}
220
221	return parseFiles(filenames)
222}
223
224func getPkgFiles(args []string) ([]*ast.File, error) {
225	if len(args) == 0 {
226		// stdin
227		file, err := parseStdin()
228		if err != nil {
229			return nil, err
230		}
231		return []*ast.File{file}, nil
232	}
233
234	if len(args) == 1 {
235		// possibly a directory
236		path := args[0]
237		info, err := os.Stat(path)
238		if err != nil {
239			return nil, err
240		}
241		if info.IsDir() {
242			return parseDir(path)
243		}
244	}
245
246	// list of files
247	return parseFiles(args)
248}
249
250func checkPkgFiles(files []*ast.File) {
251	compiler := "gc"
252	if *gccgo {
253		compiler = "gccgo"
254	}
255	type bailout struct{}
256	conf := types.Config{
257		FakeImportC: true,
258		Error: func(err error) {
259			if !*allErrors && errorCount >= 10 {
260				panic(bailout{})
261			}
262			report(err)
263		},
264		Importer: importer.For(compiler, nil),
265		Sizes:    sizes,
266	}
267
268	defer func() {
269		switch p := recover().(type) {
270		case nil, bailout:
271			// normal return or early exit
272		default:
273			// re-panic
274			panic(p)
275		}
276	}()
277
278	const path = "pkg" // any non-empty string will do for now
279	conf.Check(path, fset, files, nil)
280}
281
282func printStats(d time.Duration) {
283	fileCount := 0
284	lineCount := 0
285	fset.Iterate(func(f *token.File) bool {
286		fileCount++
287		lineCount += f.LineCount()
288		return true
289	})
290
291	fmt.Printf(
292		"%s (%d files, %d lines, %d lines/s)\n",
293		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
294	)
295}
296
297func main() {
298	flag.Usage = usage
299	flag.Parse()
300	if *printAST || *printTrace {
301		*sequential = true
302	}
303	initParserMode()
304	initSizes()
305
306	start := time.Now()
307
308	files, err := getPkgFiles(flag.Args())
309	if err != nil {
310		report(err)
311		os.Exit(2)
312	}
313
314	checkPkgFiles(files)
315	if errorCount > 0 {
316		os.Exit(2)
317	}
318
319	if *verbose {
320		printStats(time.Since(start))
321	}
322}
323