Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/gofmt/gofmt.go

Documentation: cmd/gofmt

     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"
    19  	"path/filepath"
    20  	"runtime"
    21  	"runtime/pprof"
    22  	"strings"
    23  
    24  	"cmd/internal/diff"
    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()
    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 {
    77  	var perm os.FileMode = 0644
    78  	if in == nil {
    79  		f, err := os.Open(filename)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		defer f.Close()
    84  		fi, err := f.Stat()
    85  		if err != nil {
    86  			return err
    87  		}
    88  		in = f
    89  		perm = fi.Mode().Perm()
    90  	}
    91  
    92  	src, err := ioutil.ReadAll(in)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	if rewrite != nil {
   103  		if sourceAdj == nil {
   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  
   116  	ast.Inspect(file, normalizeNumbers)
   117  
   118  	res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	if !bytes.Equal(src, res) {
   124  		// formatting has changed
   125  		if *list {
   126  			fmt.Fprintln(out, filename)
   127  		}
   128  		if *write {
   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)
   140  			if err != nil {
   141  				return err
   142  			}
   143  		}
   144  		if *doDiff {
   145  			data, err := diffWithReplaceTempFile(src, res, filename)
   146  			if err != nil {
   147  				return fmt.Errorf("computing diff: %s", err)
   148  			}
   149  			fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
   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  	}
   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) {
   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 {
   205  		if *write {
   206  			fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
   207  			exitCode = 2
   208  			return
   209  		}
   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  
   231  func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) {
   232  	data, err := diff.Diff("gofmt", b1, b2)
   233  	if len(data) > 0 {
   234  		return replaceTempFilename(data, filename)
   235  	}
   236  	return data, err
   237  }
   238  
   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
   266  }
   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
   290  	_, err = f.Write(data)
   291  	if err1 := f.Close(); err == nil {
   292  		err = err1
   293  	}
   294  
   295  	return bakname, err
   296  }
   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  }
   350  

View as plain text