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.
25 gformat "mvdan.cc/gofumpt/format"
26 "mvdan.cc/gofumpt/internal/diff"
30 // main operation modes
31 list = flag.Bool("l", false, "list files whose formatting differs from gofumpt's")
32 write = flag.Bool("w", false, "write result to (source) file instead of stdout")
33 rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')")
34 simplifyAST = flag.Bool("s", false, "simplify code")
35 doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
36 allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
39 cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
44 printerMode = printer.UseSpaces | printer.TabIndent
48 fileSet = token.NewFileSet() // per process FileSet
50 rewrite func(*ast.File) *ast.File
51 parserMode parser.Mode
54 func report(err error) {
55 scanner.PrintError(os.Stderr, err)
60 fmt.Fprintf(os.Stderr, "usage: gofumpt [flags] [path ...]\n")
64 func initParserMode() {
65 parserMode = parser.ParseComments
67 parserMode |= parser.AllErrors
71 func isGoFile(f os.FileInfo) bool {
72 // ignore non-Go files
74 return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
77 // If in == nil, the source is the contents of the file with the given filename.
78 func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
79 var perm os.FileMode = 0o644
81 f, err := os.Open(filename)
91 perm = fi.Mode().Perm()
94 src, err := ioutil.ReadAll(in)
99 file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
105 if sourceAdj == nil {
108 fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n")
112 ast.SortImports(fileSet, file)
118 ast.Inspect(file, normalizeNumbers)
120 // This is the only gofumpt change on gofumpt's codebase, besides changing
121 // the name in the usage text.
122 if *langVersion == "" {
123 out, err := exec.Command("go", "list", "-m", "-f", "{{.GoVersion}}").Output()
124 out = bytes.TrimSpace(out)
125 if err == nil && len(out) > 0 {
126 *langVersion = string(out)
129 gformat.File(fileSet, file, gformat.Options{
130 LangVersion: *langVersion,
131 ExtraRules: *extraRules,
134 res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
139 if !bytes.Equal(src, res) {
140 // formatting has changed
142 fmt.Fprintln(out, filename)
145 // make a temporary backup before overwriting original
146 bakname, err := backupFile(filename+".", src, perm)
150 err = ioutil.WriteFile(filename, res, perm)
152 os.Rename(bakname, filename)
155 err = os.Remove(bakname)
161 data, err := diffWithReplaceTempFile(src, res, filename)
163 return fmt.Errorf("computing diff: %s", err)
165 fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
170 if !*list && !*write && !*doDiff {
171 _, err = out.Write(res)
177 func visitFile(path string, f os.FileInfo, err error) error {
178 if err == nil && isGoFile(f) {
179 err = processFile(path, nil, os.Stdout, false)
181 // Don't complain if a file was deleted in the meantime (i.e.
182 // the directory changed concurrently while running gofumpt).
183 if err != nil && !os.IsNotExist(err) {
189 func walkDir(path string) {
190 filepath.Walk(path, visitFile)
194 // call gofumptMain in a separate function
195 // so that it can use defer and have them
196 // run before the exit.
205 if *cpuprofile != "" {
206 f, err := os.Create(*cpuprofile)
208 fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err)
213 pprof.StartCPUProfile(f)
214 defer pprof.StopCPUProfile()
220 if flag.NArg() == 0 {
222 fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
226 if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
232 for i := 0; i < flag.NArg(); i++ {
234 switch dir, err := os.Stat(path); {
240 if err := processFile(path, nil, os.Stdout, false); err != nil {
247 func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) {
248 data, err := diff.Diff("gofumpt", b1, b2)
250 return replaceTempFilename(data, filename)
255 // replaceTempFilename replaces temporary filenames in diff with actual one.
257 // --- /tmp/gofumpt316145376 2017-02-03 19:13:00.280468375 -0500
258 // +++ /tmp/gofumpt617882815 2017-02-03 19:13:00.280468375 -0500
261 // --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
262 // +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500
264 func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
265 bs := bytes.SplitN(diff, []byte{'\n'}, 3)
267 return nil, fmt.Errorf("got unexpected diff for %s", filename)
269 // Preserve timestamps.
271 if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
274 if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
277 // Always print filepath with slash separator.
278 f := filepath.ToSlash(filename)
279 bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
280 bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
281 return bytes.Join(bs, []byte{'\n'}), nil
284 const chmodSupported = runtime.GOOS != "windows"
286 // backupFile writes data to a new file named filename<number> with permissions perm,
287 // with <number randomly chosen such that the file name is unique. backupFile returns
288 // the chosen file name.
289 func backupFile(filename string, data []byte, perm os.FileMode) (string, error) {
290 // create backup file
291 f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
305 // write data to backup file
306 _, err = f.Write(data)
307 if err1 := f.Close(); err == nil {
314 // normalizeNumbers rewrites base prefixes and exponents to
315 // use lower-case letters, and removes leading 0's from
316 // integer imaginary literals. It leaves hexadecimal digits
318 func normalizeNumbers(n ast.Node) bool {
319 lit, _ := n.(*ast.BasicLit)
320 if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) {
323 if len(lit.Value) < 2 {
324 return false // only one digit (common case) - nothing to do
326 // len(lit.Value) >= 2
328 // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
329 // or floating-point value, decimal or not. Instead, just consider the literal pattern.
333 // 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
334 if i := strings.LastIndexByte(x, 'E'); i >= 0 {
335 x = x[:i] + "e" + x[i+1:]
338 // remove leading 0's from integer (but not floating-point) imaginary literals
339 if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
340 x = strings.TrimLeft(x, "0_")
349 // possibly a hexadecimal float
350 if i := strings.LastIndexByte(x, 'P'); i >= 0 {
351 x = x[:i] + "p" + x[i+1:]