1
2
3
4
5 package main
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "flag"
12 "fmt"
13 "go/scanner"
14 "io"
15 "log"
16 "os"
17 "os/exec"
18 "path/filepath"
19 "runtime"
20 "runtime/pprof"
21 "strings"
22
23 "golang.org/x/tools/internal/gocommand"
24 "golang.org/x/tools/internal/imports"
25 )
26
27 var (
28
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
34 verbose bool
35
36 cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
37 memProfile = flag.String("memprofile", "", "memory profile output")
38 memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
39
40 options = &imports.Options{
41 TabWidth: 8,
42 TabIndent: true,
43 Comments: true,
44 Fragment: true,
45 Env: &imports.ProcessEnv{
46 GocmdRunner: &gocommand.Runner{},
47 },
48 }
49 exitCode = 0
50 )
51
52 func init() {
53 flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
54 flag.StringVar(&options.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
55 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.")
56 }
57
58 func report(err error) {
59 scanner.PrintError(os.Stderr, err)
60 exitCode = 2
61 }
62
63 func usage() {
64 fmt.Fprintf(os.Stderr, "usage: goimports [flags] [path ...]\n")
65 flag.PrintDefaults()
66 os.Exit(2)
67 }
68
69 func isGoFile(f os.FileInfo) bool {
70
71 name := f.Name()
72 return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
73 }
74
75
76 type argumentType int
77
78 const (
79
80 fromStdin argumentType = iota
81
82
83
84 singleArg
85
86
87
88 multipleArg
89 )
90
91 func processFile(filename string, in io.Reader, out io.Writer, argType argumentType) error {
92 opt := options
93 if argType == fromStdin {
94 nopt := *options
95 nopt.Fragment = true
96 opt = &nopt
97 }
98
99 if in == nil {
100 f, err := os.Open(filename)
101 if err != nil {
102 return err
103 }
104 defer f.Close()
105 in = f
106 }
107
108 src, err := io.ReadAll(in)
109 if err != nil {
110 return err
111 }
112
113 target := filename
114 if *srcdir != "" {
115
116
117
118
119 if isFile(*srcdir) {
120 if argType == multipleArg {
121 return errors.New("-srcdir value can't be a file when passing multiple arguments or when walking directories")
122 }
123 target = *srcdir
124 } else if argType == singleArg && strings.HasSuffix(*srcdir, ".go") && !isDir(*srcdir) {
125
126
127
128
129
130
131
132
133 target = *srcdir
134 } else {
135
136
137 target = filepath.Join(*srcdir, filepath.Base(filename))
138 }
139 }
140
141 res, err := imports.Process(target, src, opt)
142 if err != nil {
143 return err
144 }
145
146 if !bytes.Equal(src, res) {
147
148 if *list {
149 fmt.Fprintln(out, filename)
150 }
151 if *write {
152 if argType == fromStdin {
153
154 return errors.New("can't use -w on stdin")
155 }
156
157 var perms os.FileMode
158 if fi, err := os.Stat(filename); err == nil {
159 perms = fi.Mode() & os.ModePerm
160 }
161 err = os.WriteFile(filename, res, perms)
162 if err != nil {
163 return err
164 }
165 }
166 if *doDiff {
167 if argType == fromStdin {
168 filename = "stdin.go"
169 }
170 data, err := diff(src, res, filename)
171 if err != nil {
172 return fmt.Errorf("computing diff: %s", err)
173 }
174 fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
175 out.Write(data)
176 }
177 }
178
179 if !*list && !*write && !*doDiff {
180 _, err = out.Write(res)
181 }
182
183 return err
184 }
185
186 func visitFile(path string, f os.FileInfo, err error) error {
187 if err == nil && isGoFile(f) {
188 err = processFile(path, nil, os.Stdout, multipleArg)
189 }
190 if err != nil {
191 report(err)
192 }
193 return nil
194 }
195
196 func walkDir(path string) {
197 filepath.Walk(path, visitFile)
198 }
199
200 func main() {
201 runtime.GOMAXPROCS(runtime.NumCPU())
202
203
204
205
206 gofmtMain()
207 os.Exit(exitCode)
208 }
209
210
211
212 var parseFlags = func() []string {
213 flag.BoolVar(&verbose, "v", false, "verbose logging")
214
215 flag.Parse()
216 return flag.Args()
217 }
218
219 func bufferedFileWriter(dest string) (w io.Writer, close func()) {
220 f, err := os.Create(dest)
221 if err != nil {
222 log.Fatal(err)
223 }
224 bw := bufio.NewWriter(f)
225 return bw, func() {
226 if err := bw.Flush(); err != nil {
227 log.Fatalf("error flushing %v: %v", dest, err)
228 }
229 if err := f.Close(); err != nil {
230 log.Fatal(err)
231 }
232 }
233 }
234
235 func gofmtMain() {
236 flag.Usage = usage
237 paths := parseFlags()
238
239 if *cpuProfile != "" {
240 bw, flush := bufferedFileWriter(*cpuProfile)
241 pprof.StartCPUProfile(bw)
242 defer flush()
243 defer pprof.StopCPUProfile()
244 }
245
246
247
248 defer doTrace()()
249 if *memProfileRate > 0 {
250 runtime.MemProfileRate = *memProfileRate
251 bw, flush := bufferedFileWriter(*memProfile)
252 defer func() {
253 runtime.GC()
254 if err := pprof.WriteHeapProfile(bw); err != nil {
255 log.Fatal(err)
256 }
257 flush()
258 }()
259 }
260
261 if verbose {
262 log.SetFlags(log.LstdFlags | log.Lmicroseconds)
263 options.Env.Logf = log.Printf
264 }
265 if options.TabWidth < 0 {
266 fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
267 exitCode = 2
268 return
269 }
270
271 if len(paths) == 0 {
272 if err := processFile("<standard input>", os.Stdin, os.Stdout, fromStdin); err != nil {
273 report(err)
274 }
275 return
276 }
277
278 argType := singleArg
279 if len(paths) > 1 {
280 argType = multipleArg
281 }
282
283 for _, path := range paths {
284 switch dir, err := os.Stat(path); {
285 case err != nil:
286 report(err)
287 case dir.IsDir():
288 walkDir(path)
289 default:
290 if err := processFile(path, nil, os.Stdout, argType); err != nil {
291 report(err)
292 }
293 }
294 }
295 }
296
297 func writeTempFile(dir, prefix string, data []byte) (string, error) {
298 file, err := os.CreateTemp(dir, prefix)
299 if err != nil {
300 return "", err
301 }
302 _, err = file.Write(data)
303 if err1 := file.Close(); err == nil {
304 err = err1
305 }
306 if err != nil {
307 os.Remove(file.Name())
308 return "", err
309 }
310 return file.Name(), nil
311 }
312
313 func diff(b1, b2 []byte, filename string) (data []byte, err error) {
314 f1, err := writeTempFile("", "gofmt", b1)
315 if err != nil {
316 return
317 }
318 defer os.Remove(f1)
319
320 f2, err := writeTempFile("", "gofmt", b2)
321 if err != nil {
322 return
323 }
324 defer os.Remove(f2)
325
326 cmd := "diff"
327 if runtime.GOOS == "plan9" {
328 cmd = "/bin/ape/diff"
329 }
330
331 data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput()
332 if len(data) > 0 {
333
334
335 return replaceTempFilename(data, filename)
336 }
337 return
338 }
339
340
341
342
343
344
345
346
347
348
349 func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
350 bs := bytes.SplitN(diff, []byte{'\n'}, 3)
351 if len(bs) < 3 {
352 return nil, fmt.Errorf("got unexpected diff for %s", filename)
353 }
354
355 var t0, t1 []byte
356 if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
357 t0 = bs[0][i:]
358 }
359 if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
360 t1 = bs[1][i:]
361 }
362
363 f := filepath.ToSlash(filename)
364 bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
365 bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
366 return bytes.Join(bs, []byte{'\n'}), nil
367 }
368
369
370 func isFile(name string) bool {
371 fi, err := os.Stat(name)
372 return err == nil && fi.Mode().IsRegular()
373 }
374
375
376 func isDir(name string) bool {
377 fi, err := os.Stat(name)
378 return err == nil && fi.IsDir()
379 }
380
View as plain text