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)
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); 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)
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)
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(), 0); 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)
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)
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(), 0)
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, 0)
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 var decls = []string{
483 `import "fmt"`,
484 "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi",
485 "func sum(x, y int) int\t{ return x + y }",
486 }
487
488 func TestDeclLists(t *testing.T) {
489 for _, src := range decls {
490 file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments)
491 if err != nil {
492 panic(err)
493 }
494
495 var buf bytes.Buffer
496 err = Fprint(&buf, fset, file.Decls)
497 if err != nil {
498 panic(err)
499 }
500
501 out := buf.String()
502 if out != src {
503 t.Errorf("\ngot : %q\nwant: %q\n", out, src)
504 }
505 }
506 }
507
508 var stmts = []string{
509 "i := 0",
510 "select {}\nvar a, b = 1, 2\nreturn a + b",
511 "go f()\ndefer func() {}()",
512 }
513
514 func TestStmtLists(t *testing.T) {
515 for _, src := range stmts {
516 file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments)
517 if err != nil {
518 panic(err)
519 }
520
521 var buf bytes.Buffer
522 err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List)
523 if err != nil {
524 panic(err)
525 }
526
527 out := buf.String()
528 if out != src {
529 t.Errorf("\ngot : %q\nwant: %q\n", out, src)
530 }
531 }
532 }
533
534 func TestBaseIndent(t *testing.T) {
535 t.Parallel()
536
537
538
539 const filename = "printer.go"
540 src, err := os.ReadFile(filename)
541 if err != nil {
542 panic(err)
543 }
544
545 file, err := parser.ParseFile(fset, filename, src, 0)
546 if err != nil {
547 panic(err)
548 }
549
550 for indent := 0; indent < 4; indent++ {
551 t.Run(fmt.Sprint(indent), func(t *testing.T) {
552 t.Parallel()
553 var buf bytes.Buffer
554 (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file)
555
556 lines := bytes.Split(buf.Bytes(), []byte{'\n'})
557 for i, line := range lines {
558 if len(line) == 0 {
559 continue
560 }
561 n := 0
562 for j, b := range line {
563 if b != '\t' {
564
565 n = j
566 break
567 }
568 }
569 if n < indent {
570 t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line)
571 }
572 }
573 })
574 }
575 }
576
577
578
579 func TestFuncType(t *testing.T) {
580 src := &ast.File{
581 Name: &ast.Ident{Name: "p"},
582 Decls: []ast.Decl{
583 &ast.FuncDecl{
584 Name: &ast.Ident{Name: "f"},
585 Type: &ast.FuncType{},
586 },
587 },
588 }
589
590 var buf bytes.Buffer
591 if err := Fprint(&buf, fset, src); err != nil {
592 t.Fatal(err)
593 }
594 got := buf.String()
595
596 const want = `package p
597
598 func f()
599 `
600
601 if got != want {
602 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
603 }
604 }
605
606
607
608
609 func TestChanType(t *testing.T) {
610 expr := &ast.UnaryExpr{
611 Op: token.ARROW,
612 X: &ast.CallExpr{
613 Fun: &ast.ChanType{
614 Dir: ast.RECV,
615 Value: &ast.Ident{Name: "int"},
616 },
617 Args: []ast.Expr{&ast.Ident{Name: "nil"}},
618 },
619 }
620 var buf bytes.Buffer
621 if err := Fprint(&buf, fset, expr); err != nil {
622 t.Fatal(err)
623 }
624 if got, want := buf.String(), `<-(<-chan int)(nil)`; got != want {
625 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
626 }
627 }
628
629 type limitWriter struct {
630 remaining int
631 errCount int
632 }
633
634 func (l *limitWriter) Write(buf []byte) (n int, err error) {
635 n = len(buf)
636 if n >= l.remaining {
637 n = l.remaining
638 err = io.EOF
639 l.errCount++
640 }
641 l.remaining -= n
642 return n, err
643 }
644
645
646 func TestWriteErrors(t *testing.T) {
647 t.Parallel()
648 const filename = "printer.go"
649 src, err := os.ReadFile(filename)
650 if err != nil {
651 panic(err)
652 }
653 file, err := parser.ParseFile(fset, filename, src, 0)
654 if err != nil {
655 panic(err)
656 }
657 for i := 0; i < 20; i++ {
658 lw := &limitWriter{remaining: i}
659 err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file)
660 if lw.errCount > 1 {
661 t.Fatal("Writes continued after first error returned")
662 }
663
664 if (lw.errCount != 0) != (err != nil) {
665 t.Fatal("Expected err when errCount != 0")
666 }
667 }
668 }
669
670
671
672 func TestX(t *testing.T) {
673 const src = `
674 package p
675 func _() {}
676 `
677 _, err := format([]byte(src), 0)
678 if err != nil {
679 t.Error(err)
680 }
681 }
682
683 func TestCommentedNode(t *testing.T) {
684 const (
685 input = `package main
686
687 func foo() {
688 // comment inside func
689 }
690
691 // leading comment
692 type bar int // comment2
693
694 `
695
696 foo = `func foo() {
697 // comment inside func
698 }`
699
700 bar = `// leading comment
701 type bar int // comment2
702 `
703 )
704
705 fset := token.NewFileSet()
706 f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments)
707 if err != nil {
708 t.Fatal(err)
709 }
710
711 var buf bytes.Buffer
712
713 err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments})
714 if err != nil {
715 t.Fatal(err)
716 }
717
718 if buf.String() != foo {
719 t.Errorf("got %q, want %q", buf.String(), foo)
720 }
721
722 buf.Reset()
723
724 err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments})
725 if err != nil {
726 t.Fatal(err)
727 }
728
729 if buf.String() != bar {
730 t.Errorf("got %q, want %q", buf.String(), bar)
731 }
732 }
733
734 func TestIssue11151(t *testing.T) {
735 const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n"
736 fset := token.NewFileSet()
737 f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
738 if err != nil {
739 t.Fatal(err)
740 }
741
742 var buf bytes.Buffer
743 Fprint(&buf, fset, f)
744 got := buf.String()
745 const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n"
746 if got != want {
747 t.Errorf("\ngot : %q\nwant: %q", got, want)
748 }
749
750
751 _, err = parser.ParseFile(fset, "", got, 0)
752 if err != nil {
753 t.Errorf("%v\norig: %q\ngot : %q", err, src, got)
754 }
755 }
756
757
758
759 func TestParenthesizedDecl(t *testing.T) {
760
761 const src = "package p; var ( a float64; b int )"
762 fset := token.NewFileSet()
763 f, err := parser.ParseFile(fset, "", src, 0)
764 if err != nil {
765 t.Fatal(err)
766 }
767
768
769 var buf bytes.Buffer
770 err = Fprint(&buf, fset, f)
771 if err != nil {
772 t.Fatal(err)
773 }
774 original := buf.String()
775
776
777 for i := 0; i != len(f.Decls); i++ {
778 f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos
779 }
780 buf.Reset()
781 err = Fprint(&buf, fset, f)
782 if err != nil {
783 t.Fatal(err)
784 }
785 noparen := buf.String()
786
787 if noparen != original {
788 t.Errorf("got %q, want %q", noparen, original)
789 }
790 }
791
792
793
794 func TestIssue32854(t *testing.T) {
795 src := `package foo
796
797 func f() {
798 return Composite{
799 call(),
800 }
801 }`
802 fset := token.NewFileSet()
803 file, err := parser.ParseFile(fset, "", src, 0)
804 if err != nil {
805 panic(err)
806 }
807
808
809 fd := file.Decls[0].(*ast.FuncDecl)
810 ret := fd.Body.List[0].(*ast.ReturnStmt)
811 ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0]
812
813 var buf bytes.Buffer
814 if err := Fprint(&buf, fset, ret); err != nil {
815 t.Fatal(err)
816 }
817 want := "return call()"
818 if got := buf.String(); got != want {
819 t.Fatalf("got %q, want %q", got, want)
820 }
821 }
822
823 func TestSourcePosNewline(t *testing.T) {
824
825
826
827
828
829
830 fname := "foo\nbar/bar.go"
831 src := `package bar`
832 fset := token.NewFileSet()
833 f, err := parser.ParseFile(fset, fname, src, parser.ParseComments|parser.AllErrors|parser.SkipObjectResolution)
834 if err != nil {
835 t.Fatal(err)
836 }
837
838 cfg := &Config{
839 Mode: SourcePos,
840 Tabwidth: 8,
841 }
842 var buf bytes.Buffer
843 if err := cfg.Fprint(&buf, fset, f); err == nil {
844 t.Errorf("Fprint did not error for source file path containing newline")
845 }
846 if buf.Len() != 0 {
847 t.Errorf("unexpected Fprint output:\n%s", buf.Bytes())
848 }
849 }
850
851
852
853
854 func TestEmptyDecl(t *testing.T) {
855 for _, tok := range []token.Token{token.IMPORT, token.CONST, token.TYPE, token.VAR} {
856 var buf bytes.Buffer
857 Fprint(&buf, token.NewFileSet(), &ast.GenDecl{Tok: tok})
858 got := buf.String()
859 want := tok.String() + " ()"
860 if got != want {
861 t.Errorf("got %q, want %q", got, want)
862 }
863 }
864 }
865
View as plain text