gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/cmd/goimports/goimports.go (about)

     1  // Copyright 2013 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  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"flag"
    12  	"fmt"
    13  	"go/scanner"
    14  	"io"
    15  	"io/ioutil"
    16  	"log"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"runtime"
    21  	"runtime/pprof"
    22  	"strings"
    23  
    24  	"golang.org/x/tools/imports"
    25  )
    26  
    27  var (
    28  	// main operation modes
    29  	list    = flag.Bool("l", false, "list files whose formatting differs from goimport's")
    30  	write   = flag.Bool("w", false, "write result to (source) file instead of stdout")
    31  	doDiff  = flag.Bool("d", false, "display diffs instead of rewriting files")
    32  	srcdir  = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
    33  	verbose bool // verbose logging
    34  
    35  	cpuProfile     = flag.String("cpuprofile", "", "CPU profile output")
    36  	memProfile     = flag.String("memprofile", "", "memory profile output")
    37  	memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
    38  
    39  	options = &imports.Options{
    40  		TabWidth:  8,
    41  		TabIndent: true,
    42  		Comments:  true,
    43  		Fragment:  true,
    44  	}
    45  	exitCode = 0
    46  )
    47  
    48  func init() {
    49  	flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
    50  	flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages")
    51  }
    52  
    53  func report(err error) {
    54  	scanner.PrintError(os.Stderr, err)
    55  	exitCode = 2
    56  }
    57  
    58  func usage() {
    59  	fmt.Fprintf(os.Stderr, "usage: goimports [flags] [path ...]\n")
    60  	flag.PrintDefaults()
    61  	os.Exit(2)
    62  }
    63  
    64  func isGoFile(f os.FileInfo) bool {
    65  	// ignore non-Go files
    66  	name := f.Name()
    67  	return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
    68  }
    69  
    70  // argumentType is which mode goimports was invoked as.
    71  type argumentType int
    72  
    73  const (
    74  	// fromStdin means the user is piping their source into goimports.
    75  	fromStdin argumentType = iota
    76  
    77  	// singleArg is the common case from editors, when goimports is run on
    78  	// a single file.
    79  	singleArg
    80  
    81  	// multipleArg is when the user ran "goimports file1.go file2.go"
    82  	// or ran goimports on a directory tree.
    83  	multipleArg
    84  )
    85  
    86  func processFile(filename string, in io.Reader, out io.Writer, argType argumentType) error {
    87  	opt := options
    88  	if argType == fromStdin {
    89  		nopt := *options
    90  		nopt.Fragment = true
    91  		opt = &nopt
    92  	}
    93  
    94  	if in == nil {
    95  		f, err := os.Open(filename)
    96  		if err != nil {
    97  			return err
    98  		}
    99  		defer f.Close()
   100  		in = f
   101  	}
   102  
   103  	src, err := ioutil.ReadAll(in)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	target := filename
   109  	if *srcdir != "" {
   110  		// Determine whether the provided -srcdirc is a directory or file
   111  		// and then use it to override the target.
   112  		//
   113  		// See https://github.com/dominikh/go-mode.el/issues/146
   114  		if isFile(*srcdir) {
   115  			if argType == multipleArg {
   116  				return errors.New("-srcdir value can't be a file when passing multiple arguments or when walking directories")
   117  			}
   118  			target = *srcdir
   119  		} else if argType == singleArg && strings.HasSuffix(*srcdir, ".go") && !isDir(*srcdir) {
   120  			// For a file which doesn't exist on disk yet, but might shortly.
   121  			// e.g. user in editor opens $DIR/newfile.go and newfile.go doesn't yet exist on disk.
   122  			// The goimports on-save hook writes the buffer to a temp file
   123  			// first and runs goimports before the actual save to newfile.go.
   124  			// The editor's buffer is named "newfile.go" so that is passed to goimports as:
   125  			//      goimports -srcdir=/gopath/src/pkg/newfile.go /tmp/gofmtXXXXXXXX.go
   126  			// and then the editor reloads the result from the tmp file and writes
   127  			// it to newfile.go.
   128  			target = *srcdir
   129  		} else {
   130  			// Pretend that file is from *srcdir in order to decide
   131  			// visible imports correctly.
   132  			target = filepath.Join(*srcdir, filepath.Base(filename))
   133  		}
   134  	}
   135  
   136  	res, err := imports.Process(target, src, opt)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	if !bytes.Equal(src, res) {
   142  		// formatting has changed
   143  		if *list {
   144  			fmt.Fprintln(out, filename)
   145  		}
   146  		if *write {
   147  			if argType == fromStdin {
   148  				// filename is "<standard input>"
   149  				return errors.New("can't use -w on stdin")
   150  			}
   151  			err = ioutil.WriteFile(filename, res, 0)
   152  			if err != nil {
   153  				return err
   154  			}
   155  		}
   156  		if *doDiff {
   157  			if argType == fromStdin {
   158  				filename = "stdin.go" // because <standard input>.orig looks silly
   159  			}
   160  			data, err := diff(src, res, filename)
   161  			if err != nil {
   162  				return fmt.Errorf("computing diff: %s", err)
   163  			}
   164  			fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
   165  			out.Write(data)
   166  		}
   167  	}
   168  
   169  	if !*list && !*write && !*doDiff {
   170  		_, err = out.Write(res)
   171  	}
   172  
   173  	return err
   174  }
   175  
   176  func visitFile(path string, f os.FileInfo, err error) error {
   177  	if err == nil && isGoFile(f) {
   178  		err = processFile(path, nil, os.Stdout, multipleArg)
   179  	}
   180  	if err != nil {
   181  		report(err)
   182  	}
   183  	return nil
   184  }
   185  
   186  func walkDir(path string) {
   187  	filepath.Walk(path, visitFile)
   188  }
   189  
   190  func main() {
   191  	runtime.GOMAXPROCS(runtime.NumCPU())
   192  
   193  	// call gofmtMain in a separate function
   194  	// so that it can use defer and have them
   195  	// run before the exit.
   196  	gofmtMain()
   197  	os.Exit(exitCode)
   198  }
   199  
   200  // parseFlags parses command line flags and returns the paths to process.
   201  // It's a var so that custom implementations can replace it in other files.
   202  var parseFlags = func() []string {
   203  	flag.BoolVar(&verbose, "v", false, "verbose logging")
   204  
   205  	flag.Parse()
   206  	return flag.Args()
   207  }
   208  
   209  func bufferedFileWriter(dest string) (w io.Writer, close func()) {
   210  	f, err := os.Create(dest)
   211  	if err != nil {
   212  		log.Fatal(err)
   213  	}
   214  	bw := bufio.NewWriter(f)
   215  	return bw, func() {
   216  		if err := bw.Flush(); err != nil {
   217  			log.Fatalf("error flushing %v: %v", dest, err)
   218  		}
   219  		if err := f.Close(); err != nil {
   220  			log.Fatal(err)
   221  		}
   222  	}
   223  }
   224  
   225  func gofmtMain() {
   226  	flag.Usage = usage
   227  	paths := parseFlags()
   228  
   229  	if *cpuProfile != "" {
   230  		bw, flush := bufferedFileWriter(*cpuProfile)
   231  		pprof.StartCPUProfile(bw)
   232  		defer flush()
   233  		defer pprof.StopCPUProfile()
   234  	}
   235  	// doTrace is a conditionally compiled wrapper around runtime/trace. It is
   236  	// used to allow goimports to compile under gccgo, which does not support
   237  	// runtime/trace. See https://golang.org/issue/15544.
   238  	defer doTrace()()
   239  	if *memProfileRate > 0 {
   240  		runtime.MemProfileRate = *memProfileRate
   241  		bw, flush := bufferedFileWriter(*memProfile)
   242  		defer func() {
   243  			runtime.GC() // materialize all statistics
   244  			if err := pprof.WriteHeapProfile(bw); err != nil {
   245  				log.Fatal(err)
   246  			}
   247  			flush()
   248  		}()
   249  	}
   250  
   251  	if verbose {
   252  		log.SetFlags(log.LstdFlags | log.Lmicroseconds)
   253  		imports.Debug = true
   254  	}
   255  	if options.TabWidth < 0 {
   256  		fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
   257  		exitCode = 2
   258  		return
   259  	}
   260  
   261  	if len(paths) == 0 {
   262  		if err := processFile("<standard input>", os.Stdin, os.Stdout, fromStdin); err != nil {
   263  			report(err)
   264  		}
   265  		return
   266  	}
   267  
   268  	argType := singleArg
   269  	if len(paths) > 1 {
   270  		argType = multipleArg
   271  	}
   272  
   273  	for _, path := range paths {
   274  		switch dir, err := os.Stat(path); {
   275  		case err != nil:
   276  			report(err)
   277  		case dir.IsDir():
   278  			walkDir(path)
   279  		default:
   280  			if err := processFile(path, nil, os.Stdout, argType); err != nil {
   281  				report(err)
   282  			}
   283  		}
   284  	}
   285  }
   286  
   287  func writeTempFile(dir, prefix string, data []byte) (string, error) {
   288  	file, err := ioutil.TempFile(dir, prefix)
   289  	if err != nil {
   290  		return "", err
   291  	}
   292  	_, err = file.Write(data)
   293  	if err1 := file.Close(); err == nil {
   294  		err = err1
   295  	}
   296  	if err != nil {
   297  		os.Remove(file.Name())
   298  		return "", err
   299  	}
   300  	return file.Name(), nil
   301  }
   302  
   303  func diff(b1, b2 []byte, filename string) (data []byte, err error) {
   304  	f1, err := writeTempFile("", "gofmt", b1)
   305  	if err != nil {
   306  		return
   307  	}
   308  	defer os.Remove(f1)
   309  
   310  	f2, err := writeTempFile("", "gofmt", b2)
   311  	if err != nil {
   312  		return
   313  	}
   314  	defer os.Remove(f2)
   315  
   316  	cmd := "diff"
   317  	if runtime.GOOS == "plan9" {
   318  		cmd = "/bin/ape/diff"
   319  	}
   320  
   321  	data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput()
   322  	if len(data) > 0 {
   323  		// diff exits with a non-zero status when the files don't match.
   324  		// Ignore that failure as long as we get output.
   325  		return replaceTempFilename(data, filename)
   326  	}
   327  	return
   328  }
   329  
   330  // replaceTempFilename replaces temporary filenames in diff with actual one.
   331  //
   332  // --- /tmp/gofmt316145376	2017-02-03 19:13:00.280468375 -0500
   333  // +++ /tmp/gofmt617882815	2017-02-03 19:13:00.280468375 -0500
   334  // ...
   335  // ->
   336  // --- path/to/file.go.orig	2017-02-03 19:13:00.280468375 -0500
   337  // +++ path/to/file.go	2017-02-03 19:13:00.280468375 -0500
   338  // ...
   339  func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
   340  	bs := bytes.SplitN(diff, []byte{'\n'}, 3)
   341  	if len(bs) < 3 {
   342  		return nil, fmt.Errorf("got unexpected diff for %s", filename)
   343  	}
   344  	// Preserve timestamps.
   345  	var t0, t1 []byte
   346  	if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
   347  		t0 = bs[0][i:]
   348  	}
   349  	if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
   350  		t1 = bs[1][i:]
   351  	}
   352  	// Always print filepath with slash separator.
   353  	f := filepath.ToSlash(filename)
   354  	bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
   355  	bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
   356  	return bytes.Join(bs, []byte{'\n'}), nil
   357  }
   358  
   359  // isFile reports whether name is a file.
   360  func isFile(name string) bool {
   361  	fi, err := os.Stat(name)
   362  	return err == nil && fi.Mode().IsRegular()
   363  }
   364  
   365  // isDir reports whether name is a directory.
   366  func isDir(name string) bool {
   367  	fi, err := os.Stat(name)
   368  	return err == nil && fi.IsDir()
   369  }