Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(3540)

Delta Between Two Patch Sets: src/cmd/govet/govet.go

Issue 4291070: code review 4291070: go/scanner: return literal as string instead of []byte (Closed)
Left Patch Set: diff -r 6659c68c1d45 https://go.googlecode.com/hg/ Created 13 years ago
Right Patch Set: diff -r 4073ecdfc054 https://go.googlecode.com/hg/ Created 13 years ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
LEFTRIGHT
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 "strconv" 18 "strconv"
19 "strings" 19 "strings"
20 "utf8" 20 "utf8"
21 ) 21 )
22 22
23 var verbose = flag.Bool("v", false, "verbose") 23 var verbose = flag.Bool("v", false, "verbose")
24 var printfuncs = flag.String("printfuncs", "", "comma-separated list of print fu nction names to check") 24 var printfuncs = flag.String("printfuncs", "", "comma-separated list of print fu nction names to check")
25 var exitCode = 0 25 var exitCode = 0
26 26
27 ÿ// setExit sets the value for os.Exit when it is called, later. Itÿ 27 // setExit sets the value for os.Exit when it is called, later. It
28 ÿ// remembers the highest value.ÿ 28 // remembers the highest value.
29 func setExit(err int) { 29 func setExit(err int) {
30 if err > exitCode { 30 if err > exitCode {
31 exitCode = err 31 exitCode = err
32 } 32 }
33 } 33 }
34 34
35 ÿ// Usage is a replacement usage function for the flags package.ÿ 35 // Usage is a replacement usage function for the flags package.
36 func Usage() { 36 func Usage() {
37 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 37 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
38 flag.PrintDefaults() 38 flag.PrintDefaults()
39 os.Exit(2) 39 os.Exit(2)
40 } 40 }
41 41
42 ÿ// File is a wrapper for the state of a file used in the parser.ÿ 42 // File is a wrapper for the state of a file used in the parser.
43 ÿ// The parse tree walkers are all methods of this type.ÿ 43 // The parse tree walkers are all methods of this type.
44 type File struct { 44 type File struct {
45 file *token.File 45 file *token.File
46 } 46 }
47 47
48 func main() { 48 func main() {
49 flag.Usage = Usage 49 flag.Usage = Usage
50 flag.Parse() 50 flag.Parse()
51 51
52 if *printfuncs != "" { 52 if *printfuncs != "" {
53 for _, name := range strings.Split(*printfuncs, ",", -1) { 53 for _, name := range strings.Split(*printfuncs, ",", -1) {
(...skipping 14 matching lines...) Expand all
68 } else { 68 } else {
69 printList[name] = skip 69 printList[name] = skip
70 } 70 }
71 } 71 }
72 } 72 }
73 73
74 if flag.NArg() == 0 { 74 if flag.NArg() == 0 {
75 doFile("stdin", os.Stdin) 75 doFile("stdin", os.Stdin)
76 } else { 76 } else {
77 for _, name := range flag.Args() { 77 for _, name := range flag.Args() {
78 » » » ÿ// Is it a directory?ÿ 78 » » » // Is it a directory?
79 if fi, err := os.Stat(name); err == nil && fi.IsDirector y() { 79 if fi, err := os.Stat(name); err == nil && fi.IsDirector y() {
80 walkDir(name) 80 walkDir(name)
81 } else { 81 } else {
82 doFile(name, nil) 82 doFile(name, nil)
83 } 83 }
84 } 84 }
85 } 85 }
86 os.Exit(exitCode) 86 os.Exit(exitCode)
87 } 87 }
88 88
89 ÿ// doFile analyzes one file. If the reader is nil, the source code is read fro m theÿ 89 // doFile analyzes one file. If the reader is nil, the source code is read from the
90 ÿ// named file.ÿ 90 // named file.
91 func doFile(name string, reader io.Reader) { 91 func doFile(name string, reader io.Reader) {
92 fs := token.NewFileSet() 92 fs := token.NewFileSet()
93 parsedFile, err := parser.ParseFile(fs, name, reader, 0) 93 parsedFile, err := parser.ParseFile(fs, name, reader, 0)
94 if err != nil { 94 if err != nil {
95 error("%s: %s", name, err) 95 error("%s: %s", name, err)
96 return 96 return
97 } 97 }
98 file := &File{fs.File(parsedFile.Pos())} 98 file := &File{fs.File(parsedFile.Pos())}
99 file.checkFile(name, parsedFile) 99 file.checkFile(name, parsedFile)
100 } 100 }
101 101
102 ÿ// Visitor for filepath.Walk - trivial. Just calls doFile on each file.ÿ 102 // Visitor for filepath.Walk - trivial. Just calls doFile on each file.
103 ÿ// TODO: if govet becomes richer, might want to processÿ 103 // TODO: if govet becomes richer, might want to process
104 ÿ// a directory (package) at a time.ÿ 104 // a directory (package) at a time.
105 type V struct{} 105 type V struct{}
106 106
107 func (v V) VisitDir(path string, f *os.FileInfo) bool { 107 func (v V) VisitDir(path string, f *os.FileInfo) bool {
108 return true 108 return true
109 } 109 }
110 110
111 func (v V) VisitFile(path string, f *os.FileInfo) { 111 func (v V) VisitFile(path string, f *os.FileInfo) {
112 if strings.HasSuffix(path, ".go") { 112 if strings.HasSuffix(path, ".go") {
113 doFile(path, nil) 113 doFile(path, nil)
114 } 114 }
115 } 115 }
116 116
117 ÿ// walkDir recursively walks the tree looking for .go files.ÿ 117 // walkDir recursively walks the tree looking for .go files.
118 func walkDir(root string) { 118 func walkDir(root string) {
119 errors := make(chan os.Error) 119 errors := make(chan os.Error)
120 done := make(chan bool) 120 done := make(chan bool)
121 go func() { 121 go func() {
122 for e := range errors { 122 for e := range errors {
123 error("walk error: %s", e) 123 error("walk error: %s", e)
124 } 124 }
125 done <- true 125 done <- true
126 }() 126 }()
127 filepath.Walk(root, V{}, errors) 127 filepath.Walk(root, V{}, errors)
128 close(errors) 128 close(errors)
129 <-done 129 <-done
130 } 130 }
131 131
132 ÿ// error formats the error to standard error, adding programÿ 132 // error formats the error to standard error, adding program
133 ÿ// identification and a newlineÿ 133 // identification and a newline
134 func error(format string, args ...interface{}) { 134 func error(format string, args ...interface{}) {
135 fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...) 135 fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...)
136 setExit(2) 136 setExit(2)
137 } 137 }
138 138
139 ÿ// Println is fmt.Println guarded by -v.ÿ 139 // Println is fmt.Println guarded by -v.
140 func Println(args ...interface{}) { 140 func Println(args ...interface{}) {
141 if !*verbose { 141 if !*verbose {
142 return 142 return
143 } 143 }
144 fmt.Println(args...) 144 fmt.Println(args...)
145 } 145 }
146 146
147 ÿ// Printf is fmt.Printf guarded by -v.ÿ 147 // Printf is fmt.Printf guarded by -v.
148 func Printf(format string, args ...interface{}) { 148 func Printf(format string, args ...interface{}) {
149 if !*verbose { 149 if !*verbose {
150 return 150 return
151 } 151 }
152 fmt.Printf(format+"\n", args...) 152 fmt.Printf(format+"\n", args...)
153 } 153 }
154 154
155 ÿ// Bad reports an error and sets the exit code..ÿ 155 // Bad reports an error and sets the exit code..
156 func (f *File) Bad(pos token.Pos, args ...interface{}) { 156 func (f *File) Bad(pos token.Pos, args ...interface{}) {
157 f.Warn(pos, args...) 157 f.Warn(pos, args...)
158 setExit(1) 158 setExit(1)
159 } 159 }
160 160
161 ÿ// Badf reports a formatted error and sets the exit code.ÿ 161 // Badf reports a formatted error and sets the exit code.
162 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 162 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
163 f.Warnf(pos, format, args...) 163 f.Warnf(pos, format, args...)
164 setExit(1) 164 setExit(1)
165 } 165 }
166 166
167 ÿ// Warn reports an error but does not set the exit code.ÿ 167 // Warn reports an error but does not set the exit code.
168 func (f *File) Warn(pos token.Pos, args ...interface{}) { 168 func (f *File) Warn(pos token.Pos, args ...interface{}) {
169 loc := f.file.Position(pos).String() + ": " 169 loc := f.file.Position(pos).String() + ": "
170 fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...)) 170 fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
171 } 171 }
172 172
173 ÿ// Warnf reports a formatted error but does not set the exit code.ÿ 173 // Warnf reports a formatted error but does not set the exit code.
174 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 174 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
175 loc := f.file.Position(pos).String() + ": " 175 loc := f.file.Position(pos).String() + ": "
176 fmt.Fprintf(os.Stderr, loc+format+"\n", args...) 176 fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
177 } 177 }
178 178
179 ÿ// checkFile checks all the top-level declarations in a file.ÿ 179 // checkFile checks all the top-level declarations in a file.
180 func (f *File) checkFile(name string, file *ast.File) { 180 func (f *File) checkFile(name string, file *ast.File) {
181 Println("Checking file", name) 181 Println("Checking file", name)
182 ast.Walk(f, file) 182 ast.Walk(f, file)
183 } 183 }
184 184
185 ÿ// Visit implements the ast.Visitor interface.ÿ 185 // Visit implements the ast.Visitor interface.
186 func (f *File) Visit(node ast.Node) ast.Visitor { 186 func (f *File) Visit(node ast.Node) ast.Visitor {
187 » ÿ// TODO: could return nil for nodes that cannot contain a CallExpr -ÿ 187 » // TODO: could return nil for nodes that cannot contain a CallExpr -
188 » ÿ// will shortcut traversal. Worthwhile?ÿ 188 » // will shortcut traversal. Worthwhile?
189 switch n := node.(type) { 189 switch n := node.(type) {
190 case *ast.CallExpr: 190 case *ast.CallExpr:
191 f.checkCallExpr(n) 191 f.checkCallExpr(n)
192 } 192 }
193 return f 193 return f
194 } 194 }
195 195
196 196
197 ÿ// checkCallExpr checks a call expression.ÿ 197 // checkCallExpr checks a call expression.
198 func (f *File) checkCallExpr(call *ast.CallExpr) { 198 func (f *File) checkCallExpr(call *ast.CallExpr) {
199 switch x := call.Fun.(type) { 199 switch x := call.Fun.(type) {
200 case *ast.Ident: 200 case *ast.Ident:
201 f.checkCall(call, x.Name) 201 f.checkCall(call, x.Name)
202 case *ast.SelectorExpr: 202 case *ast.SelectorExpr:
203 f.checkCall(call, x.Sel.Name) 203 f.checkCall(call, x.Sel.Name)
204 } 204 }
205 } 205 }
206 206
207 ÿ// printfList records the formatted-print functions. The value is the locationÿ 207 // printfList records the formatted-print functions. The value is the location
208 ÿ// of the format parameter.ÿ 208 // of the format parameter.
209 var printfList = map[string]int{ 209 var printfList = map[string]int{
210 "Errorf": 0, 210 "Errorf": 0,
211 "Fatalf": 0, 211 "Fatalf": 0,
212 "Fprintf": 1, 212 "Fprintf": 1,
213 "Panicf": 0, 213 "Panicf": 0,
214 "Printf": 0, 214 "Printf": 0,
215 "Sprintf": 0, 215 "Sprintf": 0,
216 } 216 }
217 217
218 ÿ// printList records the unformatted-print functions. The value is the location ÿ 218 // printList records the unformatted-print functions. The value is the location
219 ÿ// of the first parameter to be printed.ÿ 219 // of the first parameter to be printed.
220 var printList = map[string]int{ 220 var printList = map[string]int{
221 "Error": 0, 221 "Error": 0,
222 "Fatal": 0, 222 "Fatal": 0,
223 "Fprint": 1, "Fprintln": 1, 223 "Fprint": 1, "Fprintln": 1,
224 "Panic": 0, "Panicln": 0, 224 "Panic": 0, "Panicln": 0,
225 "Print": 0, "Println": 0, 225 "Print": 0, "Println": 0,
226 "Sprint": 0, "Sprintln": 0, 226 "Sprint": 0, "Sprintln": 0,
227 } 227 }
228 228
229 ÿ// checkCall triggers the print-specific checks if the call invokes a print fun ction.ÿ 229 // checkCall triggers the print-specific checks if the call invokes a print func tion.
230 func (f *File) checkCall(call *ast.CallExpr, name string) { 230 func (f *File) checkCall(call *ast.CallExpr, name string) {
231 if skip, ok := printfList[name]; ok { 231 if skip, ok := printfList[name]; ok {
232 f.checkPrintf(call, name, skip) 232 f.checkPrintf(call, name, skip)
233 return 233 return
234 } 234 }
235 if skip, ok := printList[name]; ok { 235 if skip, ok := printList[name]; ok {
236 f.checkPrint(call, name, skip) 236 f.checkPrint(call, name, skip)
237 return 237 return
238 } 238 }
239 } 239 }
240 240
241 ÿ// checkPrintf checks a call to a formatted print routine such as Printf.ÿ 241 // checkPrintf checks a call to a formatted print routine such as Printf.
242 ÿ// The skip argument records how many arguments to ignore; that is,ÿ 242 // The skip argument records how many arguments to ignore; that is,
243 ÿ// call.Args[skip] is (well, should be) the format argument.ÿ 243 // call.Args[skip] is (well, should be) the format argument.
244 func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) { 244 func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
245 if len(call.Args) <= skip { 245 if len(call.Args) <= skip {
246 return 246 return
247 } 247 }
248 » ÿ// Common case: literal is first argument.ÿ 248 » // Common case: literal is first argument.
249 arg := call.Args[skip] 249 arg := call.Args[skip]
250 lit, ok := arg.(*ast.BasicLit) 250 lit, ok := arg.(*ast.BasicLit)
251 if !ok { 251 if !ok {
252 » » ÿ// Too hard to check.ÿ 252 » » // Too hard to check.
253 if *verbose { 253 if *verbose {
254 f.Warn(call.Pos(), "can't check args for call to", name) 254 f.Warn(call.Pos(), "can't check args for call to", name)
255 } 255 }
256 return 256 return
257 } 257 }
258 if lit.Kind == token.STRING { 258 if lit.Kind == token.STRING {
259 if strings.Index(lit.Value, "%") < 0 { 259 if strings.Index(lit.Value, "%") < 0 {
rsc 2011/03/29 03:52:20 if strings.Contains(lit.Value, "%") { since you'r
gri 2011/03/29 04:44:34 Done.
260 if len(call.Args) > skip+1 { 260 if len(call.Args) > skip+1 {
261 f.Badf(call.Pos(), "no formatting directive in % s call", name) 261 f.Badf(call.Pos(), "no formatting directive in % s call", name)
262 } 262 }
263 return 263 return
264 } 264 }
265 } 265 }
266 » ÿ// Hard part: check formats against args.ÿ 266 » // Hard part: check formats against args.
267 » ÿ// Trivial but useful test: count.ÿ 267 » // Trivial but useful test: count.
268 numArgs := 0 268 numArgs := 0
269 for i, w := 0, 0; i < len(lit.Value); i += w { 269 for i, w := 0, 0; i < len(lit.Value); i += w {
270 w = 1 270 w = 1
271 if lit.Value[i] == '%' { 271 if lit.Value[i] == '%' {
272 nbytes, nargs := parsePrintfVerb(lit.Value[i:]) 272 nbytes, nargs := parsePrintfVerb(lit.Value[i:])
273 w = nbytes 273 w = nbytes
274 numArgs += nargs 274 numArgs += nargs
275 } 275 }
276 } 276 }
277 expect := len(call.Args) - (skip + 1) 277 expect := len(call.Args) - (skip + 1)
278 if numArgs != expect { 278 if numArgs != expect {
279 f.Badf(call.Pos(), "wrong number of args in %s call: %d needed b ut %d args", name, numArgs, expect) 279 f.Badf(call.Pos(), "wrong number of args in %s call: %d needed b ut %d args", name, numArgs, expect)
280 } 280 }
281 } 281 }
282 282
283 ÿ// parsePrintfVerb returns the number of bytes and number of argumentsÿ 283 // parsePrintfVerb returns the number of bytes and number of arguments
284 ÿ// consumed by the Printf directive that begins s, including its percent signÿ 284 // consumed by the Printf directive that begins s, including its percent sign
285 ÿ// and verb.ÿ 285 // and verb.
286 func parsePrintfVerb(s string) (nbytes, nargs int) { 286 func parsePrintfVerb(s string) (nbytes, nargs int) {
287 » ÿ// There's guaranteed a percent sign.ÿ 287 » // There's guaranteed a percent sign.
288 nbytes = 1 288 nbytes = 1
289 end := len(s) 289 end := len(s)
290 » ÿ// There may be flags.ÿ 290 » // There may be flags.
291 FlagLoop: 291 FlagLoop:
292 for nbytes < end { 292 for nbytes < end {
293 switch s[nbytes] { 293 switch s[nbytes] {
294 case '#', '0', '+', '-', ' ': 294 case '#', '0', '+', '-', ' ':
295 nbytes++ 295 nbytes++
296 default: 296 default:
297 break FlagLoop 297 break FlagLoop
298 } 298 }
299 } 299 }
300 getNum := func() { 300 getNum := func() {
301 if nbytes < end && s[nbytes] == '*' { 301 if nbytes < end && s[nbytes] == '*' {
302 nbytes++ 302 nbytes++
303 nargs++ 303 nargs++
304 } else { 304 } else {
305 for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' { 305 for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' {
306 nbytes++ 306 nbytes++
307 } 307 }
308 } 308 }
309 } 309 }
310 » ÿ// There may be a width.ÿ 310 » // There may be a width.
311 getNum() 311 getNum()
312 » ÿ// If there's a period, there may be a precision.ÿ 312 » // If there's a period, there may be a precision.
313 if nbytes < end && s[nbytes] == '.' { 313 if nbytes < end && s[nbytes] == '.' {
314 nbytes++ 314 nbytes++
315 getNum() 315 getNum()
316 } 316 }
317 » ÿ// Now a verb.ÿ 317 » // Now a verb.
318 c, w := utf8.DecodeRuneInString(s[nbytes:]) 318 c, w := utf8.DecodeRuneInString(s[nbytes:])
319 nbytes += w 319 nbytes += w
320 if c != '%' { 320 if c != '%' {
321 nargs++ 321 nargs++
322 } 322 }
323 return 323 return
324 } 324 }
325 325
326 326
327 ÿ// checkPrint checks a call to an unformatted print routine such as Println.ÿ 327 // checkPrint checks a call to an unformatted print routine such as Println.
328 ÿ// The skip argument records how many arguments to ignore; that is,ÿ 328 // The skip argument records how many arguments to ignore; that is,
329 ÿ// call.Args[skip] is the first argument to be printed.ÿ 329 // call.Args[skip] is the first argument to be printed.
330 func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) { 330 func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
331 isLn := strings.HasSuffix(name, "ln") 331 isLn := strings.HasSuffix(name, "ln")
332 args := call.Args 332 args := call.Args
333 if len(args) <= skip { 333 if len(args) <= skip {
334 if *verbose && !isLn { 334 if *verbose && !isLn {
335 f.Badf(call.Pos(), "no args in %s call", name) 335 f.Badf(call.Pos(), "no args in %s call", name)
336 } 336 }
337 return 337 return
338 } 338 }
339 arg := args[skip] 339 arg := args[skip]
340 if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { 340 if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
341 if strings.Index(lit.Value, "%") >= 0 { 341 if strings.Index(lit.Value, "%") >= 0 {
rsc 2011/03/29 03:52:20 strings.Contains
gri 2011/03/29 04:44:34 Done.
342 f.Badf(call.Pos(), "possible formatting directive in %s call", name) 342 f.Badf(call.Pos(), "possible formatting directive in %s call", name)
343 } 343 }
344 } 344 }
345 if isLn { 345 if isLn {
346 » » ÿ// The last item, if a string, should not have a newline.ÿ 346 » » // The last item, if a string, should not have a newline.
347 arg = args[len(call.Args)-1] 347 arg = args[len(call.Args)-1]
348 if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRIN G { 348 if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRIN G {
349 if strings.HasSuffix(lit.Value, `\n"`) { 349 if strings.HasSuffix(lit.Value, `\n"`) {
350 f.Badf(call.Pos(), "%s call ends with newline", name) 350 f.Badf(call.Pos(), "%s call ends with newline", name)
351 } 351 }
352 } 352 }
353 } 353 }
354 } 354 }
355 355
356 ÿ// This function never executes, but it serves as a simple test for the program .ÿ 356 // This function never executes, but it serves as a simple test for the program.
357 ÿ// Test with govet -printfuncs="Bad:1,Badf:1,Warn:1,Warnf:1" govet.goÿ 357 // Test with govet -printfuncs="Bad:1,Badf:1,Warn:1,Warnf:1" govet.go
358 func BadFunctionUsedInTests() { 358 func BadFunctionUsedInTests() {
359 » fmt.Println() ÿ// niladic callÿ 359 » fmt.Println() // niladic call
360 » fmt.Println("%s", "hi") ÿ// % in call to Printlnÿ 360 » fmt.Println("%s", "hi") // % in call to Println
361 » fmt.Printf("%s", "hi", 3) ÿ// wrong # percentsÿ 361 » fmt.Printf("%s", "hi", 3) // wrong # percents
362 » fmt.Printf("%s%%%d", "hi", 3) ÿ// right # percentsÿ 362 » fmt.Printf("%s%%%d", "hi", 3) // right # percents
363 » fmt.Printf("%.*d", 3, 3) ÿ// right # percents, with a *ÿ 363 » fmt.Printf("%.*d", 3, 3) // right # percents, with a *
364 » fmt.Printf("%.*d", 3, 3, 3) ÿ// wrong # percents, with a *ÿ 364 » fmt.Printf("%.*d", 3, 3, 3) // wrong # percents, with a *
365 » Printf("now is the time", "buddy") ÿ// no %sÿ 365 » Printf("now is the time", "buddy") // no %s
366 f := new(File) 366 f := new(File)
367 » f.Warn(0, "%s", "hello", 3) ÿ// % in call to added functionÿ 367 » f.Warn(0, "%s", "hello", 3) // % in call to added function
368 » f.Warnf(0, "%s", "hello", 3) ÿ// wrong # %s in call to added functionÿ 368 » f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function
369 } 369 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b