1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package printer 6 7import ( 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 23const ( 24 dataDir = "testdata" 25 tabwidth = 8 26) 27 28var update = flag.Bool("update", false, "update golden files") 29 30var fset = token.NewFileSet() 31 32type checkMode uint 33 34const ( 35 export checkMode = 1 << iota 36 rawFormat 37 normNumber 38 idempotent 39 allowTypeParams 40) 41 42// format parses src, prints the corresponding AST, verifies the resulting 43// src is syntactically correct, and returns the resulting src or an error 44// if any. 45func format(src []byte, mode checkMode) ([]byte, error) { 46 // parse src 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 // filter exports if necessary 53 if mode&export != 0 { 54 ast.FileExports(f) // ignore result 55 f.Comments = nil // don't print comments that are not in AST 56 } 57 58 // determine printer configuration 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 // print AST 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 // make sure formatted output is syntactically correct 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// lineAt returns the line in text starting at offset offs. 83func 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// checkEqual compares a and b. 92func 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 99func 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 // update golden files if necessary 113 if *update { 114 if err := os.WriteFile(golden, res, 0644); err != nil { 115 t.Error(err) 116 } 117 return 118 } 119 120 // get golden 121 gld, err := os.ReadFile(golden) 122 if err != nil { 123 t.Error(err) 124 return 125 } 126 127 // formatted source and golden must be the same 128 if err := checkEqual(source, golden, res, gld); err != nil { 129 t.Error(err) 130 return 131 } 132 133 if mode&idempotent != 0 { 134 // formatting golden must be idempotent 135 // (This is very difficult to achieve in general and for now 136 // it is only checked for files explicitly marked as such.) 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 148func check(t *testing.T, source, golden string, mode checkMode) { 149 // run the test 150 cc := make(chan int, 1) 151 go func() { 152 runcheck(t, source, golden, mode) 153 cc <- 0 154 }() 155 156 // wait with timeout 157 select { 158 case <-time.After(10 * time.Second): // plenty of a safety margin, even for very slow machines 159 // test running past time out 160 t.Errorf("%s: running too slowly", source) 161 case <-cc: 162 // test finished within allotted time margin 163 } 164} 165 166type entry struct { 167 source, golden string 168 mode checkMode 169} 170 171// Use go test -update to create/update the respective golden files. 172var 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 197func 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 // TODO(gri) check that golden is idempotent 207 //check(t, golden, golden, e.mode) 208 }) 209 } 210} 211 212// TestLineComments, using a simple test case, checks that consecutive line 213// comments are properly terminated with a newline even if the AST position 214// information is incorrect. 215func 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) // error in test 226 } 227 228 var buf bytes.Buffer 229 fset = token.NewFileSet() // use the wrong file set 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// Verify that the printer can be invoked during initialization. 247func 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) // error in test 252 } 253 // in debug mode, the result contains additional information; 254 // ignore it 255 if s := buf.String(); !debug && s != name { 256 panic("got " + s + ", want " + name) 257 } 258} 259 260// Verify that the printer doesn't crash if the AST contains BadXXX nodes. 261func 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") // error in test 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// testComment verifies that f can be parsed again after printing it 276// with its first comment set to comment at any possible source offset. 277func 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 // Printing f should result in a correct program no 283 // matter what the (incorrect) comment position is. 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 // Position information is just an offset. 291 // Move comment one byte down in the source. 292 comment.Slash++ 293 } 294} 295 296// Verify that the printer produces a correct program 297// even if the position information of comments introducing newlines 298// is incorrect. 299func TestBadComments(t *testing.T) { 300 t.Parallel() 301 const src = ` 302// first comment - text and position changed by test 303package p 304import "fmt" 305const pi = 3.14 // rough circle 306var ( 307 x, y, z int = 1, 2, 3 308 u, v float64 309) 310func 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) // error in test 321 } 322 323 comment := f.Comments[0].List[0] 324 pos := comment.Pos() 325 if fset.PositionFor(pos, false /* absolute position */).Offset != 1 { 326 t.Error("expected offset 1") // error in test 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 335type visitor chan *ast.Ident 336 337func (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// idents is an iterator that returns all idents in f via the result channel. 345func 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// identCount returns the number of identifiers found in f. 355func identCount(f *ast.File) int { 356 n := 0 357 for range idents(f) { 358 n++ 359 } 360 return n 361} 362 363// Verify that the SourcePos mode emits correct //line directives 364// by testing that position information for matching identifiers 365// is maintained. 366func TestSourcePos(t *testing.T) { 367 const src = ` 368package p 369import ( "go/printer"; "math" ) 370const pi = 3.14; var x = 0 371type t struct{ x, y, z int; u, v, w float32 } 372func (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 // parse original 381 f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) 382 if err != nil { 383 t.Fatal(err) 384 } 385 386 // pretty-print original 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 // parse pretty printed original 394 // (//line directives must be interpreted even w/o parser.ParseComments set) 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 // At this point the position information of identifiers in f2 should 401 // match the position information of corresponding identifiers in f1. 402 403 // number of identifiers must be > 0 (test should run) and must match 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 // verify that all identifiers have correct line information 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 // here we care about the relative (line-directive adjusted) positions 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// Verify that the SourcePos mode doesn't emit unnecessary //line directives 436// before empty lines. 437func TestIssue5945(t *testing.T) { 438 const orig = ` 439package p // line 2 440func f() {} // line 3 441 442var x, y, z int 443 444 445func g() { // line 8 446} 447` 448 449 const want = `//line src.go:2 450package p 451 452//line src.go:3 453func f() {} 454 455var x, y, z int 456 457//line src.go:8 458func g() { 459} 460` 461 462 // parse original 463 f1, err := parser.ParseFile(fset, "src.go", orig, 0) 464 if err != nil { 465 t.Fatal(err) 466 } 467 468 // pretty-print original 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 // compare original with desired output 477 if got != want { 478 t.Errorf("got:\n%s\nwant:\n%s\n", got, want) 479 } 480} 481 482var 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 488func 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) // error in test 493 } 494 495 var buf bytes.Buffer 496 err = Fprint(&buf, fset, file.Decls) // only print declarations 497 if err != nil { 498 panic(err) // error in test 499 } 500 501 out := buf.String() 502 if out != src { 503 t.Errorf("\ngot : %q\nwant: %q\n", out, src) 504 } 505 } 506} 507 508var stmts = []string{ 509 "i := 0", 510 "select {}\nvar a, b = 1, 2\nreturn a + b", 511 "go f()\ndefer func() {}()", 512} 513 514func 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) // error in test 519 } 520 521 var buf bytes.Buffer 522 err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements 523 if err != nil { 524 panic(err) // error in test 525 } 526 527 out := buf.String() 528 if out != src { 529 t.Errorf("\ngot : %q\nwant: %q\n", out, src) 530 } 531 } 532} 533 534func TestBaseIndent(t *testing.T) { 535 t.Parallel() 536 // The testfile must not contain multi-line raw strings since those 537 // are not indented (because their values must not change) and make 538 // this test fail. 539 const filename = "printer.go" 540 src, err := os.ReadFile(filename) 541 if err != nil { 542 panic(err) // error in test 543 } 544 545 file, err := parser.ParseFile(fset, filename, src, 0) 546 if err != nil { 547 panic(err) // error in test 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 // all code must be indented by at least 'indent' tabs 557 lines := bytes.Split(buf.Bytes(), []byte{'\n'}) 558 for i, line := range lines { 559 if len(line) == 0 { 560 continue // empty lines don't have indentation 561 } 562 n := 0 563 for j, b := range line { 564 if b != '\t' { 565 // end of indentation 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// TestFuncType tests that an ast.FuncType with a nil Params field 579// can be printed (per go/ast specification). Test case for issue 3870. 580func 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 599func f() 600` 601 602 if got != want { 603 t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) 604 } 605} 606 607// TestChanType tests that the tree for <-(<-chan int), without 608// ParenExpr, is correctly formatted with parens. 609// Test case for issue #63362. 610func 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 630type limitWriter struct { 631 remaining int 632 errCount int 633} 634 635func (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// Test whether the printer stops writing after the first error 647func 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) // error in test 653 } 654 file, err := parser.ParseFile(fset, filename, src, 0) 655 if err != nil { 656 panic(err) // error in test 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 // We expect errCount be 1 iff err is set 665 if (lw.errCount != 0) != (err != nil) { 666 t.Fatal("Expected err when errCount != 0") 667 } 668 } 669} 670 671// TestX is a skeleton test that can be filled in for debugging one-off cases. 672// Do not remove. 673func TestX(t *testing.T) { 674 const src = ` 675package p 676func _() {} 677` 678 _, err := format([]byte(src), 0) 679 if err != nil { 680 t.Error(err) 681 } 682} 683 684func TestCommentedNode(t *testing.T) { 685 const ( 686 input = `package main 687 688func foo() { 689 // comment inside func 690} 691 692// leading comment 693type bar int // comment2 694 695` 696 697 foo = `func foo() { 698 // comment inside func 699}` 700 701 bar = `// leading comment 702type 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 735func 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" // \r following opening /* should be stripped 747 if got != want { 748 t.Errorf("\ngot : %q\nwant: %q", got, want) 749 } 750 751 // the resulting program must be valid 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// If a declaration has multiple specifications, a parenthesized 759// declaration must be printed even if Lparen is token.NoPos. 760func TestParenthesizedDecl(t *testing.T) { 761 // a package with multiple specs in a single declaration 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 // print the original package 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 // now remove parentheses from the declaration 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// Verify that we don't print a newline between "return" and its results, as 794// that would incorrectly cause a naked return. 795func TestIssue32854(t *testing.T) { 796 src := `package foo 797 798func 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 // Replace the result with call(), which is on the next line. 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 824func TestSourcePosNewline(t *testing.T) { 825 // We don't provide a syntax for escaping or unescaping characters in line 826 // directives (see https://go.dev/issue/24183#issuecomment-372449628). 827 // As a result, we cannot write a line directive with the correct path for a 828 // filename containing newlines. We should return an error rather than 829 // silently dropping or mangling it. 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, // emit line comments 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// TestEmptyDecl tests that empty decls for const, var, import are printed with 853// valid syntax e.g "var ()" instead of just "var", which is invalid and cannot 854// be parsed. 855func TestEmptyDecl(t *testing.T) { // issue 63566 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