LEFT | RIGHT |
(no file at all) | |
1 // Copyright 2010 The Go Authors. All rights reserved. | 1 // Copyright 2010 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // Govet is a simple checker for static errors in Go source code. | 5 // Govet is a simple checker for static errors in Go source code. |
6 // See doc.go for more information. | 6 // See doc.go for more information. |
7 package main | 7 package main |
8 | 8 |
9 import ( | 9 import ( |
10 "flag" | 10 "flag" |
11 "fmt" | 11 "fmt" |
12 "io" | 12 "io" |
13 "go/ast" | 13 "go/ast" |
14 "go/parser" | 14 "go/parser" |
15 "go/token" | 15 "go/token" |
16 "os" | 16 "os" |
17 "path/filepath" | 17 "path/filepath" |
| 18 "reflect" |
18 "strconv" | 19 "strconv" |
19 "strings" | 20 "strings" |
20 "utf8" | 21 "utf8" |
21 ) | 22 ) |
22 | 23 |
23 var verbose = flag.Bool("v", false, "verbose") | 24 var verbose = flag.Bool("v", false, "verbose") |
24 var printfuncs = flag.String("printfuncs", "", "comma-separated list of print fu
nction names to check") | 25 var printfuncs = flag.String("printfuncs", "", "comma-separated list of print fu
nction names to check") |
25 var exitCode = 0 | 26 var exitCode = 0 |
26 | 27 |
27 // setExit sets the value for os.Exit when it is called, later. It | 28 // setExit sets the value for os.Exit when it is called, later. It |
(...skipping 24 matching lines...) Expand all Loading... |
52 if *printfuncs != "" { | 53 if *printfuncs != "" { |
53 for _, name := range strings.Split(*printfuncs, ",") { | 54 for _, name := range strings.Split(*printfuncs, ",") { |
54 if len(name) == 0 { | 55 if len(name) == 0 { |
55 flag.Usage() | 56 flag.Usage() |
56 } | 57 } |
57 skip := 0 | 58 skip := 0 |
58 if colon := strings.LastIndex(name, ":"); colon > 0 { | 59 if colon := strings.LastIndex(name, ":"); colon > 0 { |
59 var err os.Error | 60 var err os.Error |
60 skip, err = strconv.Atoi(name[colon+1:]) | 61 skip, err = strconv.Atoi(name[colon+1:]) |
61 if err != nil { | 62 if err != nil { |
62 » » » » » error(`illegal format for "Func:N" argum
ent %q; %s`, name, err) | 63 » » » » » errorf(`illegal format for "Func:N" argu
ment %q; %s`, name, err) |
63 } | 64 } |
64 name = name[:colon] | 65 name = name[:colon] |
65 } | 66 } |
66 name = strings.ToLower(name) | 67 name = strings.ToLower(name) |
67 if name[len(name)-1] == 'f' { | 68 if name[len(name)-1] == 'f' { |
68 printfList[name] = skip | 69 printfList[name] = skip |
69 } else { | 70 } else { |
70 printList[name] = skip | 71 printList[name] = skip |
71 } | 72 } |
72 } | 73 } |
(...skipping 13 matching lines...) Expand all Loading... |
86 } | 87 } |
87 os.Exit(exitCode) | 88 os.Exit(exitCode) |
88 } | 89 } |
89 | 90 |
90 // doFile analyzes one file. If the reader is nil, the source code is read from
the | 91 // doFile analyzes one file. If the reader is nil, the source code is read from
the |
91 // named file. | 92 // named file. |
92 func doFile(name string, reader io.Reader) { | 93 func doFile(name string, reader io.Reader) { |
93 fs := token.NewFileSet() | 94 fs := token.NewFileSet() |
94 parsedFile, err := parser.ParseFile(fs, name, reader, 0) | 95 parsedFile, err := parser.ParseFile(fs, name, reader, 0) |
95 if err != nil { | 96 if err != nil { |
96 » » error("%s: %s", name, err) | 97 » » errorf("%s: %s", name, err) |
97 return | 98 return |
98 } | 99 } |
99 file := &File{fs.File(parsedFile.Pos())} | 100 file := &File{fs.File(parsedFile.Pos())} |
100 file.checkFile(name, parsedFile) | 101 file.checkFile(name, parsedFile) |
101 } | 102 } |
102 | 103 |
103 // Visitor for filepath.Walk - trivial. Just calls doFile on each file. | 104 // Visitor for filepath.Walk - trivial. Just calls doFile on each file. |
104 // TODO: if govet becomes richer, might want to process | 105 // TODO: if govet becomes richer, might want to process |
105 // a directory (package) at a time. | 106 // a directory (package) at a time. |
106 type V struct{} | 107 type V struct{} |
107 | 108 |
108 func (v V) VisitDir(path string, f *os.FileInfo) bool { | 109 func (v V) VisitDir(path string, f *os.FileInfo) bool { |
109 return true | 110 return true |
110 } | 111 } |
111 | 112 |
112 func (v V) VisitFile(path string, f *os.FileInfo) { | 113 func (v V) VisitFile(path string, f *os.FileInfo) { |
113 if strings.HasSuffix(path, ".go") { | 114 if strings.HasSuffix(path, ".go") { |
114 doFile(path, nil) | 115 doFile(path, nil) |
115 } | 116 } |
116 } | 117 } |
117 | 118 |
118 // walkDir recursively walks the tree looking for .go files. | 119 // walkDir recursively walks the tree looking for .go files. |
119 func walkDir(root string) { | 120 func walkDir(root string) { |
120 errors := make(chan os.Error) | 121 errors := make(chan os.Error) |
121 done := make(chan bool) | 122 done := make(chan bool) |
122 go func() { | 123 go func() { |
123 for e := range errors { | 124 for e := range errors { |
124 » » » error("walk error: %s", e) | 125 » » » errorf("walk error: %s", e) |
125 } | 126 } |
126 done <- true | 127 done <- true |
127 }() | 128 }() |
128 filepath.Walk(root, V{}, errors) | 129 filepath.Walk(root, V{}, errors) |
129 close(errors) | 130 close(errors) |
130 <-done | 131 <-done |
131 } | 132 } |
132 | 133 |
133 // error formats the error to standard error, adding program | 134 // error formats the error to standard error, adding program |
134 // identification and a newline | 135 // identification and a newline |
135 func error(format string, args ...interface{}) { | 136 func errorf(format string, args ...interface{}) { |
136 fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...) | 137 fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...) |
137 setExit(2) | 138 setExit(2) |
138 } | 139 } |
139 | 140 |
140 // Println is fmt.Println guarded by -v. | 141 // Println is fmt.Println guarded by -v. |
141 func Println(args ...interface{}) { | 142 func Println(args ...interface{}) { |
142 if !*verbose { | 143 if !*verbose { |
143 return | 144 return |
144 } | 145 } |
145 fmt.Println(args...) | 146 fmt.Println(args...) |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 } | 179 } |
179 | 180 |
180 // checkFile checks all the top-level declarations in a file. | 181 // checkFile checks all the top-level declarations in a file. |
181 func (f *File) checkFile(name string, file *ast.File) { | 182 func (f *File) checkFile(name string, file *ast.File) { |
182 Println("Checking file", name) | 183 Println("Checking file", name) |
183 ast.Walk(f, file) | 184 ast.Walk(f, file) |
184 } | 185 } |
185 | 186 |
186 // Visit implements the ast.Visitor interface. | 187 // Visit implements the ast.Visitor interface. |
187 func (f *File) Visit(node ast.Node) ast.Visitor { | 188 func (f *File) Visit(node ast.Node) ast.Visitor { |
188 // TODO: could return nil for nodes that cannot contain a CallExpr - | |
189 // will shortcut traversal. Worthwhile? | |
190 switch n := node.(type) { | 189 switch n := node.(type) { |
191 case *ast.CallExpr: | 190 case *ast.CallExpr: |
192 f.checkCallExpr(n) | 191 f.checkCallExpr(n) |
| 192 case *ast.Field: |
| 193 f.checkFieldTag(n) |
193 } | 194 } |
194 return f | 195 return f |
195 } | 196 } |
196 | 197 |
| 198 // checkField checks a struct field tag. |
| 199 func (f *File) checkFieldTag(field *ast.Field) { |
| 200 if field.Tag == nil { |
| 201 return |
| 202 } |
| 203 |
| 204 tag, err := strconv.Unquote(field.Tag.Value) |
| 205 if err != nil { |
| 206 f.Warnf(field.Pos(), "unable to read struct tag %s", field.Tag.V
alue) |
| 207 return |
| 208 } |
| 209 |
| 210 // Check tag for validity by appending |
| 211 // new key:value to end and checking that |
| 212 // the tag parsing code can find it. |
| 213 if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" { |
| 214 f.Warnf(field.Pos(), "struct field tag %s not compatible with re
flect.StructTag.Get", field.Tag.Value) |
| 215 return |
| 216 } |
| 217 } |
197 | 218 |
198 // checkCallExpr checks a call expression. | 219 // checkCallExpr checks a call expression. |
199 func (f *File) checkCallExpr(call *ast.CallExpr) { | 220 func (f *File) checkCallExpr(call *ast.CallExpr) { |
200 switch x := call.Fun.(type) { | 221 switch x := call.Fun.(type) { |
201 case *ast.Ident: | 222 case *ast.Ident: |
202 f.checkCall(call, x.Name) | 223 f.checkCall(call, x.Name) |
203 case *ast.SelectorExpr: | 224 case *ast.SelectorExpr: |
204 f.checkCall(call, x.Sel.Name) | 225 f.checkCall(call, x.Sel.Name) |
205 } | 226 } |
206 } | 227 } |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
366 fmt.Printf("%s%%%d", "hi", 3) // right # percents | 387 fmt.Printf("%s%%%d", "hi", 3) // right # percents |
367 fmt.Printf("%.*d", 3, 3) // right # percents, with a * | 388 fmt.Printf("%.*d", 3, 3) // right # percents, with a * |
368 fmt.Printf("%.*d", 3, 3, 3) // wrong # percents, with a * | 389 fmt.Printf("%.*d", 3, 3, 3) // wrong # percents, with a * |
369 printf("now is the time", "buddy") // no %s | 390 printf("now is the time", "buddy") // no %s |
370 Printf("now is the time", "buddy") // no %s | 391 Printf("now is the time", "buddy") // no %s |
371 f := new(File) | 392 f := new(File) |
372 f.Warn(0, "%s", "hello", 3) // % in call to added function | 393 f.Warn(0, "%s", "hello", 3) // % in call to added function |
373 f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function | 394 f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function |
374 } | 395 } |
375 | 396 |
| 397 type BadTypeUsedInTests struct { |
| 398 X int "hello" // struct field not well-formed |
| 399 } |
| 400 |
376 // printf is used by the test. | 401 // printf is used by the test. |
377 func printf(format string, args ...interface{}) { | 402 func printf(format string, args ...interface{}) { |
378 panic("don't call - testing only") | 403 panic("don't call - testing only") |
379 } | 404 } |
LEFT | RIGHT |