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