GoPLS Viewer

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