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 }