]>
Commit | Line | Data |
---|---|---|
7d7d64c1 ILT |
1 | // Copyright 2009 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 | package main | |
6 | ||
7 | import ( | |
8 | "bytes" | |
9 | "flag" | |
10 | "fmt" | |
11 | "go/ast" | |
12 | "go/parser" | |
13 | "go/printer" | |
14 | "go/scanner" | |
15 | "go/token" | |
16 | "io" | |
17 | "io/ioutil" | |
18 | "os" | |
7d7d64c1 | 19 | "path/filepath" |
c2047754 | 20 | "runtime" |
7d7d64c1 ILT |
21 | "runtime/pprof" |
22 | "strings" | |
5a8ea165 ILT |
23 | |
24 | "cmd/internal/diff" | |
7d7d64c1 ILT |
25 | ) |
26 | ||
27 | var ( | |
28 | // main operation modes | |
29 | list = flag.Bool("l", false, "list files whose formatting differs from gofmt's") | |
30 | write = flag.Bool("w", false, "write result to (source) file instead of stdout") | |
31 | rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')") | |
32 | simplifyAST = flag.Bool("s", false, "simplify code") | |
33 | doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") | |
34 | allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)") | |
35 | ||
36 | // debugging | |
37 | cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file") | |
38 | ) | |
39 | ||
40 | const ( | |
41 | tabWidth = 8 | |
42 | printerMode = printer.UseSpaces | printer.TabIndent | |
43 | ) | |
44 | ||
45 | var ( | |
46 | fileSet = token.NewFileSet() // per process FileSet | |
47 | exitCode = 0 | |
48 | rewrite func(*ast.File) *ast.File | |
49 | parserMode parser.Mode | |
50 | ) | |
51 | ||
52 | func report(err error) { | |
53 | scanner.PrintError(os.Stderr, err) | |
54 | exitCode = 2 | |
55 | } | |
56 | ||
57 | func usage() { | |
58 | fmt.Fprintf(os.Stderr, "usage: gofmt [flags] [path ...]\n") | |
59 | flag.PrintDefaults() | |
7d7d64c1 ILT |
60 | } |
61 | ||
62 | func initParserMode() { | |
63 | parserMode = parser.ParseComments | |
64 | if *allErrors { | |
65 | parserMode |= parser.AllErrors | |
66 | } | |
67 | } | |
68 | ||
69 | func isGoFile(f os.FileInfo) bool { | |
70 | // ignore non-Go files | |
71 | name := f.Name() | |
72 | return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") | |
73 | } | |
74 | ||
75 | // If in == nil, the source is the contents of the file with the given filename. | |
76 | func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error { | |
c2047754 | 77 | var perm os.FileMode = 0644 |
7d7d64c1 ILT |
78 | if in == nil { |
79 | f, err := os.Open(filename) | |
80 | if err != nil { | |
81 | return err | |
82 | } | |
83 | defer f.Close() | |
c2047754 ILT |
84 | fi, err := f.Stat() |
85 | if err != nil { | |
86 | return err | |
87 | } | |
7d7d64c1 | 88 | in = f |
c2047754 | 89 | perm = fi.Mode().Perm() |
7d7d64c1 ILT |
90 | } |
91 | ||
92 | src, err := ioutil.ReadAll(in) | |
93 | if err != nil { | |
94 | return err | |
95 | } | |
96 | ||
f98dd1a3 | 97 | file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin) |
7d7d64c1 ILT |
98 | if err != nil { |
99 | return err | |
100 | } | |
101 | ||
102 | if rewrite != nil { | |
f8d9fa9e | 103 | if sourceAdj == nil { |
7d7d64c1 ILT |
104 | file = rewrite(file) |
105 | } else { | |
106 | fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n") | |
107 | } | |
108 | } | |
109 | ||
110 | ast.SortImports(fileSet, file) | |
111 | ||
112 | if *simplifyAST { | |
113 | simplify(file) | |
114 | } | |
115 | ||
aa8901e9 ILT |
116 | ast.Inspect(file, normalizeNumbers) |
117 | ||
f98dd1a3 | 118 | res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth}) |
7d7d64c1 ILT |
119 | if err != nil { |
120 | return err | |
121 | } | |
7d7d64c1 ILT |
122 | |
123 | if !bytes.Equal(src, res) { | |
124 | // formatting has changed | |
125 | if *list { | |
126 | fmt.Fprintln(out, filename) | |
127 | } | |
128 | if *write { | |
c2047754 ILT |
129 | // make a temporary backup before overwriting original |
130 | bakname, err := backupFile(filename+".", src, perm) | |
131 | if err != nil { | |
132 | return err | |
133 | } | |
134 | err = ioutil.WriteFile(filename, res, perm) | |
135 | if err != nil { | |
136 | os.Rename(bakname, filename) | |
137 | return err | |
138 | } | |
139 | err = os.Remove(bakname) | |
7d7d64c1 ILT |
140 | if err != nil { |
141 | return err | |
142 | } | |
143 | } | |
144 | if *doDiff { | |
5a8ea165 | 145 | data, err := diffWithReplaceTempFile(src, res, filename) |
7d7d64c1 ILT |
146 | if err != nil { |
147 | return fmt.Errorf("computing diff: %s", err) | |
148 | } | |
bc998d03 | 149 | fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) |
7d7d64c1 ILT |
150 | out.Write(data) |
151 | } | |
152 | } | |
153 | ||
154 | if !*list && !*write && !*doDiff { | |
155 | _, err = out.Write(res) | |
156 | } | |
157 | ||
158 | return err | |
159 | } | |
160 | ||
161 | func visitFile(path string, f os.FileInfo, err error) error { | |
162 | if err == nil && isGoFile(f) { | |
163 | err = processFile(path, nil, os.Stdout, false) | |
164 | } | |
22b955cc ILT |
165 | // Don't complain if a file was deleted in the meantime (i.e. |
166 | // the directory changed concurrently while running gofmt). | |
167 | if err != nil && !os.IsNotExist(err) { | |
7d7d64c1 ILT |
168 | report(err) |
169 | } | |
170 | return nil | |
171 | } | |
172 | ||
173 | func walkDir(path string) { | |
174 | filepath.Walk(path, visitFile) | |
175 | } | |
176 | ||
177 | func main() { | |
178 | // call gofmtMain in a separate function | |
179 | // so that it can use defer and have them | |
180 | // run before the exit. | |
181 | gofmtMain() | |
182 | os.Exit(exitCode) | |
183 | } | |
184 | ||
185 | func gofmtMain() { | |
186 | flag.Usage = usage | |
187 | flag.Parse() | |
188 | ||
189 | if *cpuprofile != "" { | |
190 | f, err := os.Create(*cpuprofile) | |
191 | if err != nil { | |
192 | fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err) | |
193 | exitCode = 2 | |
194 | return | |
195 | } | |
196 | defer f.Close() | |
197 | pprof.StartCPUProfile(f) | |
198 | defer pprof.StopCPUProfile() | |
199 | } | |
200 | ||
201 | initParserMode() | |
202 | initRewrite() | |
203 | ||
204 | if flag.NArg() == 0 { | |
f8d9fa9e ILT |
205 | if *write { |
206 | fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") | |
207 | exitCode = 2 | |
208 | return | |
209 | } | |
7d7d64c1 ILT |
210 | if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil { |
211 | report(err) | |
212 | } | |
213 | return | |
214 | } | |
215 | ||
216 | for i := 0; i < flag.NArg(); i++ { | |
217 | path := flag.Arg(i) | |
218 | switch dir, err := os.Stat(path); { | |
219 | case err != nil: | |
220 | report(err) | |
221 | case dir.IsDir(): | |
222 | walkDir(path) | |
223 | default: | |
224 | if err := processFile(path, nil, os.Stdout, false); err != nil { | |
225 | report(err) | |
226 | } | |
227 | } | |
228 | } | |
229 | } | |
230 | ||
5a8ea165 ILT |
231 | func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) { |
232 | data, err := diff.Diff("gofmt", b1, b2) | |
7d7d64c1 | 233 | if len(data) > 0 { |
bc998d03 | 234 | return replaceTempFilename(data, filename) |
7d7d64c1 | 235 | } |
5a8ea165 | 236 | return data, err |
bc998d03 | 237 | } |
7d7d64c1 | 238 | |
bc998d03 ILT |
239 | // replaceTempFilename replaces temporary filenames in diff with actual one. |
240 | // | |
241 | // --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500 | |
242 | // +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500 | |
243 | // ... | |
244 | // -> | |
245 | // --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500 | |
246 | // +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500 | |
247 | // ... | |
248 | func replaceTempFilename(diff []byte, filename string) ([]byte, error) { | |
249 | bs := bytes.SplitN(diff, []byte{'\n'}, 3) | |
250 | if len(bs) < 3 { | |
251 | return nil, fmt.Errorf("got unexpected diff for %s", filename) | |
252 | } | |
253 | // Preserve timestamps. | |
254 | var t0, t1 []byte | |
255 | if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 { | |
256 | t0 = bs[0][i:] | |
257 | } | |
258 | if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 { | |
259 | t1 = bs[1][i:] | |
260 | } | |
261 | // Always print filepath with slash separator. | |
262 | f := filepath.ToSlash(filename) | |
263 | bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0)) | |
264 | bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1)) | |
265 | return bytes.Join(bs, []byte{'\n'}), nil | |
7d7d64c1 | 266 | } |
c2047754 ILT |
267 | |
268 | const chmodSupported = runtime.GOOS != "windows" | |
269 | ||
270 | // backupFile writes data to a new file named filename<number> with permissions perm, | |
271 | // with <number randomly chosen such that the file name is unique. backupFile returns | |
272 | // the chosen file name. | |
273 | func backupFile(filename string, data []byte, perm os.FileMode) (string, error) { | |
274 | // create backup file | |
275 | f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)) | |
276 | if err != nil { | |
277 | return "", err | |
278 | } | |
279 | bakname := f.Name() | |
280 | if chmodSupported { | |
281 | err = f.Chmod(perm) | |
282 | if err != nil { | |
283 | f.Close() | |
284 | os.Remove(bakname) | |
285 | return bakname, err | |
286 | } | |
287 | } | |
288 | ||
289 | // write data to backup file | |
4f4a855d | 290 | _, err = f.Write(data) |
c2047754 ILT |
291 | if err1 := f.Close(); err == nil { |
292 | err = err1 | |
293 | } | |
294 | ||
295 | return bakname, err | |
296 | } | |
aa8901e9 ILT |
297 | |
298 | // normalizeNumbers rewrites base prefixes and exponents to | |
299 | // use lower-case letters, and removes leading 0's from | |
300 | // integer imaginary literals. It leaves hexadecimal digits | |
301 | // alone. | |
302 | func normalizeNumbers(n ast.Node) bool { | |
303 | lit, _ := n.(*ast.BasicLit) | |
304 | if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) { | |
305 | return true | |
306 | } | |
307 | if len(lit.Value) < 2 { | |
308 | return false // only one digit (common case) - nothing to do | |
309 | } | |
310 | // len(lit.Value) >= 2 | |
311 | ||
312 | // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer | |
313 | // or floating-point value, decimal or not. Instead, just consider the literal pattern. | |
314 | x := lit.Value | |
315 | switch x[:2] { | |
316 | default: | |
317 | // 0-prefix octal, decimal int, or float (possibly with 'i' suffix) | |
318 | if i := strings.LastIndexByte(x, 'E'); i >= 0 { | |
319 | x = x[:i] + "e" + x[i+1:] | |
320 | break | |
321 | } | |
322 | // remove leading 0's from integer (but not floating-point) imaginary literals | |
323 | if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 { | |
324 | x = strings.TrimLeft(x, "0_") | |
325 | if x == "i" { | |
326 | x = "0i" | |
327 | } | |
328 | } | |
329 | case "0X": | |
330 | x = "0x" + x[2:] | |
331 | fallthrough | |
332 | case "0x": | |
333 | // possibly a hexadecimal float | |
334 | if i := strings.LastIndexByte(x, 'P'); i >= 0 { | |
335 | x = x[:i] + "p" + x[i+1:] | |
336 | } | |
337 | case "0O": | |
338 | x = "0o" + x[2:] | |
339 | case "0o": | |
340 | // nothing to do | |
341 | case "0B": | |
342 | x = "0b" + x[2:] | |
343 | case "0b": | |
344 | // nothing to do | |
345 | } | |
346 | ||
347 | lit.Value = x | |
348 | return false | |
349 | } |