Source file
src/cmd/gofmt/gofmt.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "context"
10 "flag"
11 "fmt"
12 "go/ast"
13 "go/parser"
14 "go/printer"
15 "go/scanner"
16 "go/token"
17 "internal/diff"
18 "io"
19 "io/fs"
20 "math/rand"
21 "os"
22 "path/filepath"
23 "runtime"
24 "runtime/pprof"
25 "strconv"
26 "strings"
27
28 "cmd/internal/telemetry/counter"
29
30 "golang.org/x/sync/semaphore"
31 )
32
33 var (
34
35 list = flag.Bool("l", false, "list files whose formatting differs from gofmt's")
36 write = flag.Bool("w", false, "write result to (source) file instead of stdout")
37 rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')")
38 simplifyAST = flag.Bool("s", false, "simplify code")
39 doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
40 allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
41
42
43 cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
44 )
45
46
47 const (
48 tabWidth = 8
49 printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
50
51
52
53
54
55 printerNormalizeNumbers = 1 << 30
56 )
57
58
59
60
61
62
63
64
65
66 var fdSem = make(chan bool, 200)
67
68 var (
69 rewrite func(*token.FileSet, *ast.File) *ast.File
70 parserMode parser.Mode
71 )
72
73 func usage() {
74 fmt.Fprintf(os.Stderr, "usage: gofmt [flags] [path ...]\n")
75 flag.PrintDefaults()
76 }
77
78 func initParserMode() {
79 parserMode = parser.ParseComments
80 if *allErrors {
81 parserMode |= parser.AllErrors
82 }
83
84
85 if *rewriteRule == "" {
86 parserMode |= parser.SkipObjectResolution
87 }
88 }
89
90 func isGoFile(f fs.DirEntry) bool {
91
92 name := f.Name()
93 return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && !f.IsDir()
94 }
95
96
97
98 type sequencer struct {
99 maxWeight int64
100 sem *semaphore.Weighted
101 prev <-chan *reporterState
102 }
103
104
105
106 func newSequencer(maxWeight int64, out, err io.Writer) *sequencer {
107 sem := semaphore.NewWeighted(maxWeight)
108 prev := make(chan *reporterState, 1)
109 prev <- &reporterState{out: out, err: err}
110 return &sequencer{
111 maxWeight: maxWeight,
112 sem: sem,
113 prev: prev,
114 }
115 }
116
117
118
119 const exclusive = -1
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 func (s *sequencer) Add(weight int64, f func(*reporter) error) {
138 if weight < 0 || weight > s.maxWeight {
139 weight = s.maxWeight
140 }
141 if err := s.sem.Acquire(context.TODO(), weight); err != nil {
142
143 weight = 0
144 f = func(*reporter) error { return err }
145 }
146
147 r := &reporter{prev: s.prev}
148 next := make(chan *reporterState, 1)
149 s.prev = next
150
151
152
153 go func() {
154 if err := f(r); err != nil {
155 r.Report(err)
156 }
157 next <- r.getState()
158 s.sem.Release(weight)
159 }()
160 }
161
162
163
164 func (s *sequencer) AddReport(err error) {
165 s.Add(0, func(*reporter) error { return err })
166 }
167
168
169
170 func (s *sequencer) GetExitCode() int {
171 c := make(chan int, 1)
172 s.Add(0, func(r *reporter) error {
173 c <- r.ExitCode()
174 return nil
175 })
176 return <-c
177 }
178
179
180 type reporter struct {
181 prev <-chan *reporterState
182 state *reporterState
183 }
184
185
186
187
188 type reporterState struct {
189 out, err io.Writer
190 exitCode int
191 }
192
193
194
195 func (r *reporter) getState() *reporterState {
196 if r.state == nil {
197 r.state = <-r.prev
198 }
199 return r.state
200 }
201
202
203
204 func (r *reporter) Warnf(format string, args ...any) {
205 fmt.Fprintf(r.getState().err, format, args...)
206 }
207
208
209
210
211
212 func (r *reporter) Write(p []byte) (int, error) {
213 return r.getState().out.Write(p)
214 }
215
216
217
218 func (r *reporter) Report(err error) {
219 if err == nil {
220 panic("Report with nil error")
221 }
222 st := r.getState()
223 scanner.PrintError(st.err, err)
224 st.exitCode = 2
225 }
226
227 func (r *reporter) ExitCode() int {
228 return r.getState().exitCode
229 }
230
231
232
233 func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) error {
234 src, err := readFile(filename, info, in)
235 if err != nil {
236 return err
237 }
238
239 fileSet := token.NewFileSet()
240
241
242 fragmentOk := info == nil
243 file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, fragmentOk)
244 if err != nil {
245 return err
246 }
247
248 if rewrite != nil {
249 if sourceAdj == nil {
250 file = rewrite(fileSet, file)
251 } else {
252 r.Warnf("warning: rewrite ignored for incomplete programs\n")
253 }
254 }
255
256 ast.SortImports(fileSet, file)
257
258 if *simplifyAST {
259 simplify(file)
260 }
261
262 res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
263 if err != nil {
264 return err
265 }
266
267 if !bytes.Equal(src, res) {
268
269 if *list {
270 fmt.Fprintln(r, filename)
271 }
272 if *write {
273 if info == nil {
274 panic("-w should not have been allowed with stdin")
275 }
276
277 perm := info.Mode().Perm()
278 if err := writeFile(filename, src, res, perm, info.Size()); err != nil {
279 return err
280 }
281 }
282 if *doDiff {
283 newName := filepath.ToSlash(filename)
284 oldName := newName + ".orig"
285 r.Write(diff.Diff(oldName, src, newName, res))
286 }
287 }
288
289 if !*list && !*write && !*doDiff {
290 _, err = r.Write(res)
291 }
292
293 return err
294 }
295
296
297
298
299
300 func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) {
301 if in == nil {
302 fdSem <- true
303 var err error
304 f, err := os.Open(filename)
305 if err != nil {
306 return nil, err
307 }
308 in = f
309 defer func() {
310 f.Close()
311 <-fdSem
312 }()
313 }
314
315
316
317
318
319
320
321
322 size := -1
323 if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() {
324 size = int(info.Size())
325 }
326 if size+1 <= 0 {
327
328 var err error
329 src, err := io.ReadAll(in)
330 if err != nil {
331 return nil, err
332 }
333 return src, nil
334 }
335
336
337
338
339
340
341
342 src := make([]byte, size+1)
343 n, err := io.ReadFull(in, src)
344 switch err {
345 case nil, io.EOF, io.ErrUnexpectedEOF:
346
347
348
349 default:
350 return nil, err
351 }
352 if n < size {
353 return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
354 } else if n > size {
355 return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
356 }
357 return src[:n], nil
358 }
359
360 func main() {
361
362
363
364
365
366 maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0))
367 s := newSequencer(maxWeight, os.Stdout, os.Stderr)
368
369
370
371
372 gofmtMain(s)
373 os.Exit(s.GetExitCode())
374 }
375
376 func gofmtMain(s *sequencer) {
377 counter.Open()
378 flag.Usage = usage
379 flag.Parse()
380 counter.Inc("gofmt/invocations")
381 counter.CountFlags("gofmt/flag:", *flag.CommandLine)
382
383 if *cpuprofile != "" {
384 fdSem <- true
385 f, err := os.Create(*cpuprofile)
386 if err != nil {
387 s.AddReport(fmt.Errorf("creating cpu profile: %s", err))
388 return
389 }
390 defer func() {
391 f.Close()
392 <-fdSem
393 }()
394 pprof.StartCPUProfile(f)
395 defer pprof.StopCPUProfile()
396 }
397
398 initParserMode()
399 initRewrite()
400
401 args := flag.Args()
402 if len(args) == 0 {
403 if *write {
404 s.AddReport(fmt.Errorf("error: cannot use -w with standard input"))
405 return
406 }
407 s.Add(0, func(r *reporter) error {
408 return processFile("<standard input>", nil, os.Stdin, r)
409 })
410 return
411 }
412
413 for _, arg := range args {
414 switch info, err := os.Stat(arg); {
415 case err != nil:
416 s.AddReport(err)
417 case !info.IsDir():
418
419 arg := arg
420 s.Add(fileWeight(arg, info), func(r *reporter) error {
421 return processFile(arg, info, nil, r)
422 })
423 default:
424
425 err := filepath.WalkDir(arg, func(path string, f fs.DirEntry, err error) error {
426 if err != nil || !isGoFile(f) {
427 return err
428 }
429 info, err := f.Info()
430 if err != nil {
431 s.AddReport(err)
432 return nil
433 }
434 s.Add(fileWeight(path, info), func(r *reporter) error {
435 return processFile(path, info, nil, r)
436 })
437 return nil
438 })
439 if err != nil {
440 s.AddReport(err)
441 }
442 }
443 }
444 }
445
446 func fileWeight(path string, info fs.FileInfo) int64 {
447 if info == nil {
448 return exclusive
449 }
450 if info.Mode().Type() == fs.ModeSymlink {
451 var err error
452 info, err = os.Stat(path)
453 if err != nil {
454 return exclusive
455 }
456 }
457 if !info.Mode().IsRegular() {
458
459
460 return exclusive
461 }
462 return info.Size()
463 }
464
465
466 func writeFile(filename string, orig, formatted []byte, perm fs.FileMode, size int64) error {
467
468 bakname, err := backupFile(filename, orig, perm)
469 if err != nil {
470 return err
471 }
472
473 fdSem <- true
474 defer func() { <-fdSem }()
475
476 fout, err := os.OpenFile(filename, os.O_WRONLY, perm)
477 if err != nil {
478
479
480 os.Remove(bakname)
481 return err
482 }
483 defer fout.Close()
484
485 restoreFail := func(err error) {
486 fmt.Fprintf(os.Stderr, "gofmt: %s: error restoring file to original: %v; backup in %s\n", filename, err, bakname)
487 }
488
489 n, err := fout.Write(formatted)
490 if err == nil && int64(n) < size {
491 err = fout.Truncate(int64(n))
492 }
493
494 if err != nil {
495
496
497 if n == 0 {
498
499 os.Remove(bakname)
500 return err
501 }
502
503
504
505 no, erro := fout.WriteAt(orig, 0)
506 if erro != nil {
507
508 restoreFail(erro)
509 return err
510 }
511
512 if no < n {
513
514 if erro = fout.Truncate(int64(no)); erro != nil {
515 restoreFail(erro)
516 return err
517 }
518 }
519
520 if erro := fout.Close(); erro != nil {
521 restoreFail(erro)
522 return err
523 }
524
525
526 os.Remove(bakname)
527 return err
528 }
529
530 if err := fout.Close(); err != nil {
531 restoreFail(err)
532 return err
533 }
534
535
536 os.Remove(bakname)
537 return nil
538 }
539
540
541
542
543 func backupFile(filename string, data []byte, perm fs.FileMode) (string, error) {
544 fdSem <- true
545 defer func() { <-fdSem }()
546
547 nextRandom := func() string {
548 return strconv.Itoa(rand.Int())
549 }
550
551 dir, base := filepath.Split(filename)
552 var (
553 bakname string
554 f *os.File
555 )
556 for {
557 bakname = filepath.Join(dir, base+"."+nextRandom())
558 var err error
559 f, err = os.OpenFile(bakname, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
560 if err == nil {
561 break
562 }
563 if !os.IsExist(err) {
564 return "", err
565 }
566 }
567
568
569 _, err := f.Write(data)
570 if err1 := f.Close(); err == nil {
571 err = err1
572 }
573
574 return bakname, err
575 }
576
View as plain text