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