Source file
src/go/printer/printer_test.go
1
2
3
4
5 package printer
6
7 import (
8 "bytes"
9 "errors"
10 "flag"
11 "fmt"
12 "go/ast"
13 "go/parser"
14 "go/token"
15 "internal/diff"
16 "io"
17 "os"
18 "path/filepath"
19 "testing"
20 "time"
21 )
22
23 const (
24 dataDir = "testdata"
25 tabwidth = 8
26 )
27
28 var update = flag.Bool("update", false, "update golden files")
29
30 var fset = token.NewFileSet()
31
32 type checkMode uint
33
34 const (
35 export checkMode = 1 << iota
36 rawFormat
37 normNumber
38 idempotent
39 allowTypeParams
40 )
41
42
43
44
45 func format(src []byte, mode checkMode) ([]byte, error) {
46
47 f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution)
48 if err != nil {
49 return nil, fmt.Errorf("parse: %s\n%s", err, src)
50 }
51
52
53 if mode&export != 0 {
54 ast.FileExports(f)
55 f.Comments = nil
56 }
57
58
59 cfg := Config{Tabwidth: tabwidth}
60 if mode&rawFormat != 0 {
61 cfg.Mode |= RawFormat
62 }
63 if mode&normNumber != 0 {
64 cfg.Mode |= normalizeNumbers
65 }
66
67
68 var buf bytes.Buffer
69 if err := cfg.Fprint(&buf, fset, f); err != nil {
70 return nil, fmt.Errorf("print: %s", err)
71 }
72
73
74 res := buf.Bytes()
75 if _, err := parser.ParseFile(fset, "", res, parser.ParseComments|parser.SkipObjectResolution); err != nil {
76 return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes())
77 }
78
79 return res, nil
80 }
81
82
83 func lineAt(text []byte, offs int) []byte {
84 i := offs
85 for i < len(text) && text[i] != '\n' {
86 i++
87 }
88 return text[offs:i]
89 }
90
91
92 func checkEqual(aname, bname string, a, b []byte) error {
93 if bytes.Equal(a, b) {
94 return nil
95 }
96 return errors.New(string(diff.Diff(aname, a, bname, b)))
97 }
98
99 func runcheck(t *testing.T, source, golden string, mode checkMode) {
100 src, err := os.ReadFile(source)
101 if err != nil {
102 t.Error(err)
103 return
104 }
105
106 res, err := format(src, mode)
107 if err != nil {
108 t.Error(err)
109 return
110 }
111
112
113 if *update {
114 if err := os.WriteFile(golden, res, 0644); err != nil {
115 t.Error(err)
116 }
117 return
118 }
119
120
121 gld, err := os.ReadFile(golden)
122 if err != nil {
123 t.Error(err)
124 return
125 }
126
127
128 if err := checkEqual(fmt.Sprintf("format(%v)", source), golden, res, gld); err != nil {
129 t.Error(err)
130 return
131 }
132
133 if mode&idempotent != 0 {
134
135
136
137 res, err = format(gld, mode)
138 if err != nil {
139 t.Error(err)
140 return
141 }
142 if err := checkEqual(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil {
143 t.Errorf("golden is not idempotent: %s", err)
144 }
145 }
146 }
147
148 func check(t *testing.T, source, golden string, mode checkMode) {
149
150 cc := make(chan int, 1)
151 go func() {
152 runcheck(t, source, golden, mode)
153 cc <- 0
154 }()
155
156
157 select {
158 case <-time.After(10 * time.Second):
159
160 t.Errorf("%s: running too slowly", source)
161 case <-cc:
162
163 }
164 }
165
166 type entry struct {
167 source, golden string
168 mode checkMode
169 }
170
171
172 var data = []entry{
173 {"empty.input", "empty.golden", idempotent},
174 {"comments.input", "comments.golden", 0},
175 {"comments.input", "comments.x", export},
176 {"comments2.input", "comments2.golden", idempotent},
177 {"alignment.input", "alignment.golden", idempotent},
178 {"linebreaks.input", "linebreaks.golden", idempotent},
179 {"expressions.input", "expressions.golden", idempotent},
180 {"expressions.input", "expressions.raw", rawFormat | idempotent},
181 {"declarations.input", "declarations.golden", 0},
182 {"statements.input", "statements.golden", 0},
183 {"slow.input", "slow.golden", idempotent},
184 {"complit.input", "complit.x", export},
185 {"go2numbers.input", "go2numbers.golden", idempotent},
186 {"go2numbers.input", "go2numbers.norm", normNumber | idempotent},
187 {"generics.input", "generics.golden", idempotent | allowTypeParams},
188 {"gobuild1.input", "gobuild1.golden", idempotent},
189 {"gobuild2.input", "gobuild2.golden", idempotent},
190 {"gobuild3.input", "gobuild3.golden", idempotent},
191 {"gobuild4.input", "gobuild4.golden", idempotent},
192 {"gobuild5.input", "gobuild5.golden", idempotent},
193 {"gobuild6.input", "gobuild6.golden", idempotent},
194 {"gobuild7.input", "gobuild7.golden", idempotent},
195 }
196
197 func TestFiles(t *testing.T) {
198 t.Parallel()
199 for _, e := range data {
200 source := filepath.Join(dataDir, e.source)
201 golden := filepath.Join(dataDir, e.golden)
202 mode := e.mode
203 t.Run(e.source, func(t *testing.T) {
204 t.Parallel()
205 check(t, source, golden, mode)
206
207
208 })
209 }
210 }
211
212
213
214
215 func TestLineComments(t *testing.T) {
216 const src = `// comment 1
217 // comment 2
218 // comment 3
219 package main
220 `
221
222 fset := token.NewFileSet()
223 f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution)
224 if err != nil {
225 panic(err)
226 }
227
228 var buf bytes.Buffer
229 fset = token.NewFileSet()
230 Fprint(&buf, fset, f)
231
232 nlines := 0
233 for _, ch := range buf.Bytes() {
234 if ch == '\n' {
235 nlines++
236 }
237 }
238
239 const expected = 3
240 if nlines < expected {
241 t.Errorf("got %d, expected %d\n", nlines, expected)
242 t.Errorf("result:\n%s", buf.Bytes())
243 }
244 }
245
246
247 func init() {
248 const name = "foobar"
249 var buf bytes.Buffer
250 if err := Fprint(&buf, fset, &ast.Ident{Name: name}); err != nil {
251 panic(err)
252 }
253
254
255 if s := buf.String(); !debug && s != name {
256 panic("got " + s + ", want " + name)
257 }
258 }
259
260
261 func TestBadNodes(t *testing.T) {
262 const src = "package p\n("
263 const res = "package p\nBadDecl\n"
264 f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution)
265 if err == nil {
266 t.Error("expected illegal program")
267 }
268 var buf bytes.Buffer
269 Fprint(&buf, fset, f)
270 if buf.String() != res {
271 t.Errorf("got %q, expected %q", buf.String(), res)
272 }
273 }
274
275
276
277 func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) {
278 f.Comments[0].List[0] = comment
279 var buf bytes.Buffer
280 for offs := 0; offs <= srclen; offs++ {
281 buf.Reset()
282
283
284 if err := Fprint(&buf, fset, f); err != nil {
285 t.Error(err)
286 }
287 if _, err := parser.ParseFile(fset, "", buf.Bytes(), parser.SkipObjectResolution); err != nil {
288 t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String())
289 }
290
291
292 comment.Slash++
293 }
294 }
295
296
297
298
299 func TestBadComments(t *testing.T) {
300 t.Parallel()
301 const src = `
302 // first comment - text and position changed by test
303 package p
304 import "fmt"
305 const pi = 3.14 // rough circle
306 var (
307 x, y, z int = 1, 2, 3
308 u, v float64
309 )
310 func fibo(n int) {
311 if n < 2 {
312 return n /* seed values */
313 }
314 return fibo(n-1) + fibo(n-2)
315 }
316 `
317
318 f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution)
319 if err != nil {
320 t.Error(err)
321 }
322
323 comment := f.Comments[0].List[0]
324 pos := comment.Pos()
325 if fset.PositionFor(pos, false ).Offset != 1 {
326 t.Error("expected offset 1")
327 }
328
329 testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"})
330 testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"})
331 testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"})
332 testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"})
333 }
334
335 type visitor chan *ast.Ident
336
337 func (v visitor) Visit(n ast.Node) (w ast.Visitor) {
338 if ident, ok := n.(*ast.Ident); ok {
339 v <- ident
340 }
341 return v
342 }
343
344
345 func idents(f *ast.File) <-chan *ast.Ident {
346 v := make(visitor)
347 go func() {
348 ast.Walk(v, f)
349 close(v)
350 }()
351 return v
352 }
353
354
355 func identCount(f *ast.File) int {
356 n := 0
357 for range idents(f) {
358 n++
359 }
360 return n
361 }
362
363
364
365
366 func TestSourcePos(t *testing.T) {
367 const src = `
368 package p
369 import ( "go/printer"; "math" )
370 const pi = 3.14; var x = 0
371 type t struct{ x, y, z int; u, v, w float32 }
372 func (t *t) foo(a, b, c int) int {
373 return a*t.x + b*t.y +
374 // two extra lines here
375 // ...
376 c*t.z
377 }
378 `
379
380
381 f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments|parser.SkipObjectResolution)
382 if err != nil {
383 t.Fatal(err)
384 }
385
386
387 var buf bytes.Buffer
388 err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1)
389 if err != nil {
390 t.Fatal(err)
391 }
392
393
394
395 f2, err := parser.ParseFile(fset, "", buf.Bytes(), parser.SkipObjectResolution)
396 if err != nil {
397 t.Fatalf("%s\n%s", err, buf.Bytes())
398 }
399
400
401
402
403
404 n1 := identCount(f1)
405 n2 := identCount(f2)
406 if n1 == 0 {
407 t.Fatal("got no idents")
408 }
409 if n2 != n1 {
410 t.Errorf("got %d idents; want %d", n2, n1)
411 }
412
413
414 i2range := idents(f2)
415 for i1 := range idents(f1) {
416 i2 := <-i2range
417
418 if i2.Name != i1.Name {
419 t.Errorf("got ident %s; want %s", i2.Name, i1.Name)
420 }
421
422
423 l1 := fset.Position(i1.Pos()).Line
424 l2 := fset.Position(i2.Pos()).Line
425 if l2 != l1 {
426 t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name)
427 }
428 }
429
430 if t.Failed() {
431 t.Logf("\n%s", buf.Bytes())
432 }
433 }
434
435
436
437 func TestIssue5945(t *testing.T) {
438 const orig = `
439 package p // line 2
440 func f() {} // line 3
441
442 var x, y, z int
443
444
445 func g() { // line 8
446 }
447 `
448
449 const want = `//line src.go:2
450 package p
451
452 //line src.go:3
453 func f() {}
454
455 var x, y, z int
456
457 //line src.go:8
458 func g() {
459 }
460 `
461
462
463 f1, err := parser.ParseFile(fset, "src.go", orig, parser.SkipObjectResolution)
464 if err != nil {
465 t.Fatal(err)
466 }
467
468
469 var buf bytes.Buffer
470 err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1)
471 if err != nil {
472 t.Fatal(err)
473 }
474 got := buf.String()
475
476
477 if got != want {
478 t.Errorf("got:\n%s\nwant:\n%s\n", got, want)
479 }
480 }
481
482 func TestIssue52605(t *testing.T) {
483 const orig = `
484 package p
485
486 // Doc
487 //
488 type T struct {
489 // This is not
490 // a doc comment.
491 X int
492 }
493 `
494
495 const want = `package p
496
497 // Doc
498 type T struct {
499 // This is not
500 // a doc comment.
501 X int
502 }
503 `
504
505 f, err := parser.ParseFile(fset, "src.go", orig, parser.ParseComments|parser.SkipObjectResolution)
506 if err != nil {
507 t.Fatal(err)
508 }
509
510 var buf bytes.Buffer
511 err = Fprint(&buf, fset, f)
512 if err != nil {
513 t.Fatal(err)
514 }
515
516 got := buf.String()
517 if got != want {
518 t.Errorf("got:\n%s\nwant:\n%s\n", got, want)
519 }
520 }
521
522 var decls = []string{
523 `import "fmt"`,
524 "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi",
525 "func sum(x, y int) int\t{ return x + y }",
526 }
527
528 func TestDeclLists(t *testing.T) {
529 for _, src := range decls {
530 file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments|parser.SkipObjectResolution)
531 if err != nil {
532 panic(err)
533 }
534
535 var buf bytes.Buffer
536 err = Fprint(&buf, fset, file.Decls)
537 if err != nil {
538 panic(err)
539 }
540
541 out := buf.String()
542 if out != src {
543 t.Errorf("\ngot : %q\nwant: %q\n", out, src)
544 }
545 }
546 }
547
548 var stmts = []string{
549 "i := 0",
550 "select {}\nvar a, b = 1, 2\nreturn a + b",
551 "go f()\ndefer func() {}()",
552 }
553
554 func TestStmtLists(t *testing.T) {
555 for _, src := range stmts {
556 file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments|parser.SkipObjectResolution)
557 if err != nil {
558 panic(err)
559 }
560
561 var buf bytes.Buffer
562 err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List)
563 if err != nil {
564 panic(err)
565 }
566
567 out := buf.String()
568 if out != src {
569 t.Errorf("\ngot : %q\nwant: %q\n", out, src)
570 }
571 }
572 }
573
574 func TestBaseIndent(t *testing.T) {
575 t.Parallel()
576
577
578
579 const filename = "printer.go"
580 src, err := os.ReadFile(filename)
581 if err != nil {
582 panic(err)
583 }
584
585 file, err := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution)
586 if err != nil {
587 panic(err)
588 }
589
590 for indent := 0; indent < 4; indent++ {
591 t.Run(fmt.Sprint(indent), func(t *testing.T) {
592 t.Parallel()
593 var buf bytes.Buffer
594 (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file)
595
596 lines := bytes.Split(buf.Bytes(), []byte{'\n'})
597 for i, line := range lines {
598 if len(line) == 0 {
599 continue
600 }
601 n := 0
602 for j, b := range line {
603 if b != '\t' {
604
605 n = j
606 break
607 }
608 }
609 if n < indent {
610 t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line)
611 }
612 }
613 })
614 }
615 }
616
617
618
619 func TestFuncType(t *testing.T) {
620 src := &ast.File{
621 Name: &ast.Ident{Name: "p"},
622 Decls: []ast.Decl{
623 &ast.FuncDecl{
624 Name: &ast.Ident{Name: "f"},
625 Type: &ast.FuncType{},
626 },
627 },
628 }
629
630 var buf bytes.Buffer
631 if err := Fprint(&buf, fset, src); err != nil {
632 t.Fatal(err)
633 }
634 got := buf.String()
635
636 const want = `package p
637
638 func f()
639 `
640
641 if got != want {
642 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
643 }
644 }
645
646
647
648
649 func TestChanType(t *testing.T) {
650 expr := &ast.UnaryExpr{
651 Op: token.ARROW,
652 X: &ast.CallExpr{
653 Fun: &ast.ChanType{
654 Dir: ast.RECV,
655 Value: &ast.Ident{Name: "int"},
656 },
657 Args: []ast.Expr{&ast.Ident{Name: "nil"}},
658 },
659 }
660 var buf bytes.Buffer
661 if err := Fprint(&buf, fset, expr); err != nil {
662 t.Fatal(err)
663 }
664 if got, want := buf.String(), `<-(<-chan int)(nil)`; got != want {
665 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
666 }
667 }
668
669 type limitWriter struct {
670 remaining int
671 errCount int
672 }
673
674 func (l *limitWriter) Write(buf []byte) (n int, err error) {
675 n = len(buf)
676 if n >= l.remaining {
677 n = l.remaining
678 err = io.EOF
679 l.errCount++
680 }
681 l.remaining -= n
682 return n, err
683 }
684
685
686 func TestWriteErrors(t *testing.T) {
687 t.Parallel()
688 const filename = "printer.go"
689 src, err := os.ReadFile(filename)
690 if err != nil {
691 panic(err)
692 }
693 file, err := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution)
694 if err != nil {
695 panic(err)
696 }
697 for i := 0; i < 20; i++ {
698 lw := &limitWriter{remaining: i}
699 err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file)
700 if lw.errCount > 1 {
701 t.Fatal("Writes continued after first error returned")
702 }
703
704 if (lw.errCount != 0) != (err != nil) {
705 t.Fatal("Expected err when errCount != 0")
706 }
707 }
708 }
709
710
711
712 func TestX(t *testing.T) {
713 const src = `
714 package p
715 func _() {}
716 `
717 _, err := format([]byte(src), 0)
718 if err != nil {
719 t.Error(err)
720 }
721 }
722
723 func TestCommentedNode(t *testing.T) {
724 const (
725 input = `package main
726
727 func foo() {
728 // comment inside func
729 }
730
731 // leading comment
732 type bar int // comment2
733
734 `
735
736 foo = `func foo() {
737 // comment inside func
738 }`
739
740 bar = `// leading comment
741 type bar int // comment2
742 `
743 )
744
745 fset := token.NewFileSet()
746 f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments|parser.SkipObjectResolution)
747 if err != nil {
748 t.Fatal(err)
749 }
750
751 var buf bytes.Buffer
752
753 err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments})
754 if err != nil {
755 t.Fatal(err)
756 }
757
758 if buf.String() != foo {
759 t.Errorf("got %q, want %q", buf.String(), foo)
760 }
761
762 buf.Reset()
763
764 err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments})
765 if err != nil {
766 t.Fatal(err)
767 }
768
769 if buf.String() != bar {
770 t.Errorf("got %q, want %q", buf.String(), bar)
771 }
772 }
773
774 func TestIssue11151(t *testing.T) {
775 const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n"
776 fset := token.NewFileSet()
777 f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution)
778 if err != nil {
779 t.Fatal(err)
780 }
781
782 var buf bytes.Buffer
783 Fprint(&buf, fset, f)
784 got := buf.String()
785 const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n"
786 if got != want {
787 t.Errorf("\ngot : %q\nwant: %q", got, want)
788 }
789
790
791 _, err = parser.ParseFile(fset, "", got, parser.SkipObjectResolution)
792 if err != nil {
793 t.Errorf("%v\norig: %q\ngot : %q", err, src, got)
794 }
795 }
796
797
798
799 func TestParenthesizedDecl(t *testing.T) {
800
801 const src = "package p; var ( a float64; b int )"
802 fset := token.NewFileSet()
803 f, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution)
804 if err != nil {
805 t.Fatal(err)
806 }
807
808
809 var buf bytes.Buffer
810 err = Fprint(&buf, fset, f)
811 if err != nil {
812 t.Fatal(err)
813 }
814 original := buf.String()
815
816
817 for i := 0; i != len(f.Decls); i++ {
818 f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos
819 }
820 buf.Reset()
821 err = Fprint(&buf, fset, f)
822 if err != nil {
823 t.Fatal(err)
824 }
825 noparen := buf.String()
826
827 if noparen != original {
828 t.Errorf("got %q, want %q", noparen, original)
829 }
830 }
831
832
833
834 func TestIssue32854(t *testing.T) {
835 src := `package foo
836
837 func f() {
838 return Composite{
839 call(),
840 }
841 }`
842 fset := token.NewFileSet()
843 file, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution)
844 if err != nil {
845 panic(err)
846 }
847
848
849 fd := file.Decls[0].(*ast.FuncDecl)
850 ret := fd.Body.List[0].(*ast.ReturnStmt)
851 ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0]
852
853 var buf bytes.Buffer
854 if err := Fprint(&buf, fset, ret); err != nil {
855 t.Fatal(err)
856 }
857 want := "return call()"
858 if got := buf.String(); got != want {
859 t.Fatalf("got %q, want %q", got, want)
860 }
861 }
862
863 func TestSourcePosNewline(t *testing.T) {
864
865
866
867
868
869
870 fname := "foo\nbar/bar.go"
871 src := `package bar`
872 fset := token.NewFileSet()
873 f, err := parser.ParseFile(fset, fname, src, parser.ParseComments|parser.AllErrors|parser.SkipObjectResolution)
874 if err != nil {
875 t.Fatal(err)
876 }
877
878 cfg := &Config{
879 Mode: SourcePos,
880 Tabwidth: 8,
881 }
882 var buf bytes.Buffer
883 if err := cfg.Fprint(&buf, fset, f); err == nil {
884 t.Errorf("Fprint did not error for source file path containing newline")
885 }
886 if buf.Len() != 0 {
887 t.Errorf("unexpected Fprint output:\n%s", buf.Bytes())
888 }
889 }
890
891
892
893
894 func TestEmptyDecl(t *testing.T) {
895 for _, tok := range []token.Token{token.IMPORT, token.CONST, token.TYPE, token.VAR} {
896 var buf bytes.Buffer
897 Fprint(&buf, token.NewFileSet(), &ast.GenDecl{Tok: tok})
898 got := buf.String()
899 want := tok.String() + " ()"
900 if got != want {
901 t.Errorf("got %q, want %q", got, want)
902 }
903 }
904 }
905
906
907
908 func TestIssue7195(t *testing.T) {
909 const src = `package p
910 type T struct{ x int }
911 func _() (T, *T) {
912 return T{
913 x: 1,
914 }, &T{
915 x: 2,
916 }
917 }
918 `
919
920 const want = `package p
921
922 type T struct{ x int }
923
924 func _() (T, *T) {
925 return T{
926 x: 1,
927 }, &T{
928 x: 2,
929 }
930 }
931 `
932
933 got, err := format([]byte(src), 0)
934 if err != nil {
935 t.Fatal(err)
936 }
937 if got := string(got); got != want {
938 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
939 }
940 }
941
View as plain text