+++ /dev/null
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/parser"
- "go/printer"
- "go/scanner"
- "go/token"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "runtime/pprof"
- "strings"
-
- gformat "mvdan.cc/gofumpt/format"
- "mvdan.cc/gofumpt/internal/diff"
-)
-
-var (
- // main operation modes
- list = flag.Bool("l", false, "list files whose formatting differs from gofumpt's")
- write = flag.Bool("w", false, "write result to (source) file instead of stdout")
- rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')")
- simplifyAST = flag.Bool("s", false, "simplify code")
- doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
- allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
-
- // debugging
- cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
-)
-
-const (
- tabWidth = 8
- printerMode = printer.UseSpaces | printer.TabIndent
-)
-
-var (
- fileSet = token.NewFileSet() // per process FileSet
- exitCode = 0
- rewrite func(*ast.File) *ast.File
- parserMode parser.Mode
-)
-
-func report(err error) {
- scanner.PrintError(os.Stderr, err)
- exitCode = 2
-}
-
-func usage() {
- fmt.Fprintf(os.Stderr, "usage: gofumpt [flags] [path ...]\n")
- flag.PrintDefaults()
-}
-
-func initParserMode() {
- parserMode = parser.ParseComments
- if *allErrors {
- parserMode |= parser.AllErrors
- }
-}
-
-func isGoFile(f os.FileInfo) bool {
- // ignore non-Go files
- name := f.Name()
- return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
-}
-
-// If in == nil, the source is the contents of the file with the given filename.
-func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
- var perm os.FileMode = 0o644
- if in == nil {
- f, err := os.Open(filename)
- if err != nil {
- return err
- }
- defer f.Close()
- fi, err := f.Stat()
- if err != nil {
- return err
- }
- in = f
- perm = fi.Mode().Perm()
- }
-
- src, err := ioutil.ReadAll(in)
- if err != nil {
- return err
- }
-
- file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
- if err != nil {
- return err
- }
-
- if rewrite != nil {
- if sourceAdj == nil {
- file = rewrite(file)
- } else {
- fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n")
- }
- }
-
- ast.SortImports(fileSet, file)
-
- if *simplifyAST {
- simplify(file)
- }
-
- ast.Inspect(file, normalizeNumbers)
-
- // This is the only gofumpt change on gofumpt's codebase, besides changing
- // the name in the usage text.
- if *langVersion == "" {
- out, err := exec.Command("go", "list", "-m", "-f", "{{.GoVersion}}").Output()
- out = bytes.TrimSpace(out)
- if err == nil && len(out) > 0 {
- *langVersion = string(out)
- }
- }
- gformat.File(fileSet, file, gformat.Options{
- LangVersion: *langVersion,
- ExtraRules: *extraRules,
- })
-
- res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
- if err != nil {
- return err
- }
-
- if !bytes.Equal(src, res) {
- // formatting has changed
- if *list {
- fmt.Fprintln(out, filename)
- }
- if *write {
- // make a temporary backup before overwriting original
- bakname, err := backupFile(filename+".", src, perm)
- if err != nil {
- return err
- }
- err = ioutil.WriteFile(filename, res, perm)
- if err != nil {
- os.Rename(bakname, filename)
- return err
- }
- err = os.Remove(bakname)
- if err != nil {
- return err
- }
- }
- if *doDiff {
- data, err := diffWithReplaceTempFile(src, res, filename)
- if err != nil {
- return fmt.Errorf("computing diff: %s", err)
- }
- fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
- out.Write(data)
- }
- }
-
- if !*list && !*write && !*doDiff {
- _, err = out.Write(res)
- }
-
- return err
-}
-
-func visitFile(path string, f os.FileInfo, err error) error {
- if err == nil && isGoFile(f) {
- err = processFile(path, nil, os.Stdout, false)
- }
- // Don't complain if a file was deleted in the meantime (i.e.
- // the directory changed concurrently while running gofumpt).
- if err != nil && !os.IsNotExist(err) {
- report(err)
- }
- return nil
-}
-
-func walkDir(path string) {
- filepath.Walk(path, visitFile)
-}
-
-func main() {
- // call gofumptMain in a separate function
- // so that it can use defer and have them
- // run before the exit.
- gofumptMain()
- os.Exit(exitCode)
-}
-
-func gofumptMain() {
- flag.Usage = usage
- flag.Parse()
-
- if *cpuprofile != "" {
- f, err := os.Create(*cpuprofile)
- if err != nil {
- fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err)
- exitCode = 2
- return
- }
- defer f.Close()
- pprof.StartCPUProfile(f)
- defer pprof.StopCPUProfile()
- }
-
- initParserMode()
- initRewrite()
-
- if flag.NArg() == 0 {
- if *write {
- fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
- exitCode = 2
- return
- }
- if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
- report(err)
- }
- return
- }
-
- for i := 0; i < flag.NArg(); i++ {
- path := flag.Arg(i)
- switch dir, err := os.Stat(path); {
- case err != nil:
- report(err)
- case dir.IsDir():
- walkDir(path)
- default:
- if err := processFile(path, nil, os.Stdout, false); err != nil {
- report(err)
- }
- }
- }
-}
-
-func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) {
- data, err := diff.Diff("gofumpt", b1, b2)
- if len(data) > 0 {
- return replaceTempFilename(data, filename)
- }
- return data, err
-}
-
-// replaceTempFilename replaces temporary filenames in diff with actual one.
-//
-// --- /tmp/gofumpt316145376 2017-02-03 19:13:00.280468375 -0500
-// +++ /tmp/gofumpt617882815 2017-02-03 19:13:00.280468375 -0500
-// ...
-// ->
-// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
-// +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500
-// ...
-func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
- bs := bytes.SplitN(diff, []byte{'\n'}, 3)
- if len(bs) < 3 {
- return nil, fmt.Errorf("got unexpected diff for %s", filename)
- }
- // Preserve timestamps.
- var t0, t1 []byte
- if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
- t0 = bs[0][i:]
- }
- if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
- t1 = bs[1][i:]
- }
- // Always print filepath with slash separator.
- f := filepath.ToSlash(filename)
- bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
- bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
- return bytes.Join(bs, []byte{'\n'}), nil
-}
-
-const chmodSupported = runtime.GOOS != "windows"
-
-// backupFile writes data to a new file named filename<number> with permissions perm,
-// with <number randomly chosen such that the file name is unique. backupFile returns
-// the chosen file name.
-func backupFile(filename string, data []byte, perm os.FileMode) (string, error) {
- // create backup file
- f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
- if err != nil {
- return "", err
- }
- bakname := f.Name()
- if chmodSupported {
- err = f.Chmod(perm)
- if err != nil {
- f.Close()
- os.Remove(bakname)
- return bakname, err
- }
- }
-
- // write data to backup file
- _, err = f.Write(data)
- if err1 := f.Close(); err == nil {
- err = err1
- }
-
- return bakname, err
-}
-
-// normalizeNumbers rewrites base prefixes and exponents to
-// use lower-case letters, and removes leading 0's from
-// integer imaginary literals. It leaves hexadecimal digits
-// alone.
-func normalizeNumbers(n ast.Node) bool {
- lit, _ := n.(*ast.BasicLit)
- if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) {
- return true
- }
- if len(lit.Value) < 2 {
- return false // only one digit (common case) - nothing to do
- }
- // len(lit.Value) >= 2
-
- // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
- // or floating-point value, decimal or not. Instead, just consider the literal pattern.
- x := lit.Value
- switch x[:2] {
- default:
- // 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
- if i := strings.LastIndexByte(x, 'E'); i >= 0 {
- x = x[:i] + "e" + x[i+1:]
- break
- }
- // remove leading 0's from integer (but not floating-point) imaginary literals
- if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
- x = strings.TrimLeft(x, "0_")
- if x == "i" {
- x = "0i"
- }
- }
- case "0X":
- x = "0x" + x[2:]
- fallthrough
- case "0x":
- // possibly a hexadecimal float
- if i := strings.LastIndexByte(x, 'P'); i >= 0 {
- x = x[:i] + "p" + x[i+1:]
- }
- case "0O":
- x = "0o" + x[2:]
- case "0o":
- // nothing to do
- case "0B":
- x = "0b" + x[2:]
- case "0b":
- // nothing to do
- }
-
- lit.Value = x
- return false
-}