Source file src/go/printer/printer_test.go

     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  
     5  package printer
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/parser"
    14  	"go/token"
    15  	"io"
    16  	"os"
    17  	"path/filepath"
    18  	"testing"
    19  	"time"
    20  )
    21  
    22  const (
    23  	dataDir  = "testdata"
    24  	tabwidth = 8
    25  )
    26  
    27  var update = flag.Bool("update", false, "update golden files")
    28  
    29  var fset = token.NewFileSet()
    30  
    31  type checkMode uint
    32  
    33  const (
    34  	export checkMode = 1 << iota
    35  	rawFormat
    36  	normNumber
    37  	idempotent
    38  	allowTypeParams
    39  )
    40  
    41  // format parses src, prints the corresponding AST, verifies the resulting
    42  // src is syntactically correct, and returns the resulting src or an error
    43  // if any.
    44  func format(src []byte, mode checkMode) ([]byte, error) {
    45  	// parse src
    46  	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("parse: %s\n%s", err, src)
    49  	}
    50  
    51  	// filter exports if necessary
    52  	if mode&export != 0 {
    53  		ast.FileExports(f) // ignore result
    54  		f.Comments = nil   // don't print comments that are not in AST
    55  	}
    56  
    57  	// determine printer configuration
    58  	cfg := Config{Tabwidth: tabwidth}
    59  	if mode&rawFormat != 0 {
    60  		cfg.Mode |= RawFormat
    61  	}
    62  	if mode&normNumber != 0 {
    63  		cfg.Mode |= normalizeNumbers
    64  	}
    65  
    66  	// print AST
    67  	var buf bytes.Buffer
    68  	if err := cfg.Fprint(&buf, fset, f); err != nil {
    69  		return nil, fmt.Errorf("print: %s", err)
    70  	}
    71  
    72  	// make sure formatted output is syntactically correct
    73  	res := buf.Bytes()
    74  	if _, err := parser.ParseFile(fset, "", res, parser.ParseComments); err != nil {
    75  		return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes())
    76  	}
    77  
    78  	return res, nil
    79  }
    80  
    81  // lineAt returns the line in text starting at offset offs.
    82  func lineAt(text []byte, offs int) []byte {
    83  	i := offs
    84  	for i < len(text) && text[i] != '\n' {
    85  		i++
    86  	}
    87  	return text[offs:i]
    88  }
    89  
    90  // diff compares a and b.
    91  func diff(aname, bname string, a, b []byte) error {
    92  	if bytes.Equal(a, b) {
    93  		return nil
    94  	}
    95  
    96  	var buf bytes.Buffer // holding long error message
    97  	// compare lengths
    98  	if len(a) != len(b) {
    99  		fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b))
   100  	}
   101  
   102  	// compare contents
   103  	line := 1
   104  	offs := 0
   105  	for i := 0; i < len(a) && i < len(b); i++ {
   106  		ch := a[i]
   107  		if ch != b[i] {
   108  			fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs))
   109  			fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs))
   110  			fmt.Fprintf(&buf, "\n\n")
   111  			break
   112  		}
   113  		if ch == '\n' {
   114  			line++
   115  			offs = i + 1
   116  		}
   117  	}
   118  
   119  	fmt.Fprintf(&buf, "\n%s:\n%s\n%s:\n%s", aname, a, bname, b)
   120  	return errors.New(buf.String())
   121  }
   122  
   123  func runcheck(t *testing.T, source, golden string, mode checkMode) {
   124  	src, err := os.ReadFile(source)
   125  	if err != nil {
   126  		t.Error(err)
   127  		return
   128  	}
   129  
   130  	res, err := format(src, mode)
   131  	if err != nil {
   132  		t.Error(err)
   133  		return
   134  	}
   135  
   136  	// update golden files if necessary
   137  	if *update {
   138  		if err := os.WriteFile(golden, res, 0644); err != nil {
   139  			t.Error(err)
   140  		}
   141  		return
   142  	}
   143  
   144  	// get golden
   145  	gld, err := os.ReadFile(golden)
   146  	if err != nil {
   147  		t.Error(err)
   148  		return
   149  	}
   150  
   151  	// formatted source and golden must be the same
   152  	if err := diff(source, golden, res, gld); err != nil {
   153  		t.Error(err)
   154  		return
   155  	}
   156  
   157  	if mode&idempotent != 0 {
   158  		// formatting golden must be idempotent
   159  		// (This is very difficult to achieve in general and for now
   160  		// it is only checked for files explicitly marked as such.)
   161  		res, err = format(gld, mode)
   162  		if err != nil {
   163  			t.Error(err)
   164  			return
   165  		}
   166  		if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil {
   167  			t.Errorf("golden is not idempotent: %s", err)
   168  		}
   169  	}
   170  }
   171  
   172  func check(t *testing.T, source, golden string, mode checkMode) {
   173  	// run the test
   174  	cc := make(chan int, 1)
   175  	go func() {
   176  		runcheck(t, source, golden, mode)
   177  		cc <- 0
   178  	}()
   179  
   180  	// wait with timeout
   181  	select {
   182  	case <-time.After(10 * time.Second): // plenty of a safety margin, even for very slow machines
   183  		// test running past time out
   184  		t.Errorf("%s: running too slowly", source)
   185  	case <-cc:
   186  		// test finished within allotted time margin
   187  	}
   188  }
   189  
   190  type entry struct {
   191  	source, golden string
   192  	mode           checkMode
   193  }
   194  
   195  // Use go test -update to create/update the respective golden files.
   196  var data = []entry{
   197  	{"empty.input", "empty.golden", idempotent},
   198  	{"comments.input", "comments.golden", 0},
   199  	{"comments.input", "comments.x", export},
   200  	{"comments2.input", "comments2.golden", idempotent},
   201  	{"alignment.input", "alignment.golden", idempotent},
   202  	{"linebreaks.input", "linebreaks.golden", idempotent},
   203  	{"expressions.input", "expressions.golden", idempotent},
   204  	{"expressions.input", "expressions.raw", rawFormat | idempotent},
   205  	{"declarations.input", "declarations.golden", 0},
   206  	{"statements.input", "statements.golden", 0},
   207  	{"slow.input", "slow.golden", idempotent},
   208  	{"complit.input", "complit.x", export},
   209  	{"go2numbers.input", "go2numbers.golden", idempotent},
   210  	{"go2numbers.input", "go2numbers.norm", normNumber | idempotent},
   211  	{"generics.input", "generics.golden", idempotent | allowTypeParams},
   212  	{"gobuild1.input", "gobuild1.golden", idempotent},
   213  	{"gobuild2.input", "gobuild2.golden", idempotent},
   214  	{"gobuild3.input", "gobuild3.golden", idempotent},
   215  	{"gobuild4.input", "gobuild4.golden", idempotent},
   216  	{"gobuild5.input", "gobuild5.golden", idempotent},
   217  	{"gobuild6.input", "gobuild6.golden", idempotent},
   218  	{"gobuild7.input", "gobuild7.golden", idempotent},
   219  }
   220  
   221  func TestFiles(t *testing.T) {
   222  	t.Parallel()
   223  	for _, e := range data {
   224  		source := filepath.Join(dataDir, e.source)
   225  		golden := filepath.Join(dataDir, e.golden)
   226  		mode := e.mode
   227  		t.Run(e.source, func(t *testing.T) {
   228  			t.Parallel()
   229  			check(t, source, golden, mode)
   230  			// TODO(gri) check that golden is idempotent
   231  			//check(t, golden, golden, e.mode)
   232  		})
   233  	}
   234  }
   235  
   236  // TestLineComments, using a simple test case, checks that consecutive line
   237  // comments are properly terminated with a newline even if the AST position
   238  // information is incorrect.
   239  //
   240  func TestLineComments(t *testing.T) {
   241  	const src = `// comment 1
   242  	// comment 2
   243  	// comment 3
   244  	package main
   245  	`
   246  
   247  	fset := token.NewFileSet()
   248  	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
   249  	if err != nil {
   250  		panic(err) // error in test
   251  	}
   252  
   253  	var buf bytes.Buffer
   254  	fset = token.NewFileSet() // use the wrong file set
   255  	Fprint(&buf, fset, f)
   256  
   257  	nlines := 0
   258  	for _, ch := range buf.Bytes() {
   259  		if ch == '\n' {
   260  			nlines++
   261  		}
   262  	}
   263  
   264  	const expected = 3
   265  	if nlines < expected {
   266  		t.Errorf("got %d, expected %d\n", nlines, expected)
   267  		t.Errorf("result:\n%s", buf.Bytes())
   268  	}
   269  }
   270  
   271  // Verify that the printer can be invoked during initialization.
   272  func init() {
   273  	const name = "foobar"
   274  	var buf bytes.Buffer
   275  	if err := Fprint(&buf, fset, &ast.Ident{Name: name}); err != nil {
   276  		panic(err) // error in test
   277  	}
   278  	// in debug mode, the result contains additional information;
   279  	// ignore it
   280  	if s := buf.String(); !debug && s != name {
   281  		panic("got " + s + ", want " + name)
   282  	}
   283  }
   284  
   285  // Verify that the printer doesn't crash if the AST contains BadXXX nodes.
   286  func TestBadNodes(t *testing.T) {
   287  	const src = "package p\n("
   288  	const res = "package p\nBadDecl\n"
   289  	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
   290  	if err == nil {
   291  		t.Error("expected illegal program") // error in test
   292  	}
   293  	var buf bytes.Buffer
   294  	Fprint(&buf, fset, f)
   295  	if buf.String() != res {
   296  		t.Errorf("got %q, expected %q", buf.String(), res)
   297  	}
   298  }
   299  
   300  // testComment verifies that f can be parsed again after printing it
   301  // with its first comment set to comment at any possible source offset.
   302  func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) {
   303  	f.Comments[0].List[0] = comment
   304  	var buf bytes.Buffer
   305  	for offs := 0; offs <= srclen; offs++ {
   306  		buf.Reset()
   307  		// Printing f should result in a correct program no
   308  		// matter what the (incorrect) comment position is.
   309  		if err := Fprint(&buf, fset, f); err != nil {
   310  			t.Error(err)
   311  		}
   312  		if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil {
   313  			t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String())
   314  		}
   315  		// Position information is just an offset.
   316  		// Move comment one byte down in the source.
   317  		comment.Slash++
   318  	}
   319  }
   320  
   321  // Verify that the printer produces a correct program
   322  // even if the position information of comments introducing newlines
   323  // is incorrect.
   324  func TestBadComments(t *testing.T) {
   325  	t.Parallel()
   326  	const src = `
   327  // first comment - text and position changed by test
   328  package p
   329  import "fmt"
   330  const pi = 3.14 // rough circle
   331  var (
   332  	x, y, z int = 1, 2, 3
   333  	u, v float64
   334  )
   335  func fibo(n int) {
   336  	if n < 2 {
   337  		return n /* seed values */
   338  	}
   339  	return fibo(n-1) + fibo(n-2)
   340  }
   341  `
   342  
   343  	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
   344  	if err != nil {
   345  		t.Error(err) // error in test
   346  	}
   347  
   348  	comment := f.Comments[0].List[0]
   349  	pos := comment.Pos()
   350  	if fset.PositionFor(pos, false /* absolute position */).Offset != 1 {
   351  		t.Error("expected offset 1") // error in test
   352  	}
   353  
   354  	testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"})
   355  	testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"})
   356  	testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"})
   357  	testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"})
   358  }
   359  
   360  type visitor chan *ast.Ident
   361  
   362  func (v visitor) Visit(n ast.Node) (w ast.Visitor) {
   363  	if ident, ok := n.(*ast.Ident); ok {
   364  		v <- ident
   365  	}
   366  	return v
   367  }
   368  
   369  // idents is an iterator that returns all idents in f via the result channel.
   370  func idents(f *ast.File) <-chan *ast.Ident {
   371  	v := make(visitor)
   372  	go func() {
   373  		ast.Walk(v, f)
   374  		close(v)
   375  	}()
   376  	return v
   377  }
   378  
   379  // identCount returns the number of identifiers found in f.
   380  func identCount(f *ast.File) int {
   381  	n := 0
   382  	for range idents(f) {
   383  		n++
   384  	}
   385  	return n
   386  }
   387  
   388  // Verify that the SourcePos mode emits correct //line directives
   389  // by testing that position information for matching identifiers
   390  // is maintained.
   391  func TestSourcePos(t *testing.T) {
   392  	const src = `
   393  package p
   394  import ( "go/printer"; "math" )
   395  const pi = 3.14; var x = 0
   396  type t struct{ x, y, z int; u, v, w float32 }
   397  func (t *t) foo(a, b, c int) int {
   398  	return a*t.x + b*t.y +
   399  		// two extra lines here
   400  		// ...
   401  		c*t.z
   402  }
   403  `
   404  
   405  	// parse original
   406  	f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments)
   407  	if err != nil {
   408  		t.Fatal(err)
   409  	}
   410  
   411  	// pretty-print original
   412  	var buf bytes.Buffer
   413  	err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  
   418  	// parse pretty printed original
   419  	// (//line directives must be interpreted even w/o parser.ParseComments set)
   420  	f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0)
   421  	if err != nil {
   422  		t.Fatalf("%s\n%s", err, buf.Bytes())
   423  	}
   424  
   425  	// At this point the position information of identifiers in f2 should
   426  	// match the position information of corresponding identifiers in f1.
   427  
   428  	// number of identifiers must be > 0 (test should run) and must match
   429  	n1 := identCount(f1)
   430  	n2 := identCount(f2)
   431  	if n1 == 0 {
   432  		t.Fatal("got no idents")
   433  	}
   434  	if n2 != n1 {
   435  		t.Errorf("got %d idents; want %d", n2, n1)
   436  	}
   437  
   438  	// verify that all identifiers have correct line information
   439  	i2range := idents(f2)
   440  	for i1 := range idents(f1) {
   441  		i2 := <-i2range
   442  
   443  		if i2.Name != i1.Name {
   444  			t.Errorf("got ident %s; want %s", i2.Name, i1.Name)
   445  		}
   446  
   447  		// here we care about the relative (line-directive adjusted) positions
   448  		l1 := fset.Position(i1.Pos()).Line
   449  		l2 := fset.Position(i2.Pos()).Line
   450  		if l2 != l1 {
   451  			t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name)
   452  		}
   453  	}
   454  
   455  	if t.Failed() {
   456  		t.Logf("\n%s", buf.Bytes())
   457  	}
   458  }
   459  
   460  // Verify that the SourcePos mode doesn't emit unnecessary //line directives
   461  // before empty lines.
   462  func TestIssue5945(t *testing.T) {
   463  	const orig = `
   464  package p   // line 2
   465  func f() {} // line 3
   466  
   467  var x, y, z int
   468  
   469  
   470  func g() { // line 8
   471  }
   472  `
   473  
   474  	const want = `//line src.go:2
   475  package p
   476  
   477  //line src.go:3
   478  func f() {}
   479  
   480  var x, y, z int
   481  
   482  //line src.go:8
   483  func g() {
   484  }
   485  `
   486  
   487  	// parse original
   488  	f1, err := parser.ParseFile(fset, "src.go", orig, 0)
   489  	if err != nil {
   490  		t.Fatal(err)
   491  	}
   492  
   493  	// pretty-print original
   494  	var buf bytes.Buffer
   495  	err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1)
   496  	if err != nil {
   497  		t.Fatal(err)
   498  	}
   499  	got := buf.String()
   500  
   501  	// compare original with desired output
   502  	if got != want {
   503  		t.Errorf("got:\n%s\nwant:\n%s\n", got, want)
   504  	}
   505  }
   506  
   507  var decls = []string{
   508  	`import "fmt"`,
   509  	"const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi",
   510  	"func sum(x, y int) int\t{ return x + y }",
   511  }
   512  
   513  func TestDeclLists(t *testing.T) {
   514  	for _, src := range decls {
   515  		file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments)
   516  		if err != nil {
   517  			panic(err) // error in test
   518  		}
   519  
   520  		var buf bytes.Buffer
   521  		err = Fprint(&buf, fset, file.Decls) // only print declarations
   522  		if err != nil {
   523  			panic(err) // error in test
   524  		}
   525  
   526  		out := buf.String()
   527  		if out != src {
   528  			t.Errorf("\ngot : %q\nwant: %q\n", out, src)
   529  		}
   530  	}
   531  }
   532  
   533  var stmts = []string{
   534  	"i := 0",
   535  	"select {}\nvar a, b = 1, 2\nreturn a + b",
   536  	"go f()\ndefer func() {}()",
   537  }
   538  
   539  func TestStmtLists(t *testing.T) {
   540  	for _, src := range stmts {
   541  		file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments)
   542  		if err != nil {
   543  			panic(err) // error in test
   544  		}
   545  
   546  		var buf bytes.Buffer
   547  		err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements
   548  		if err != nil {
   549  			panic(err) // error in test
   550  		}
   551  
   552  		out := buf.String()
   553  		if out != src {
   554  			t.Errorf("\ngot : %q\nwant: %q\n", out, src)
   555  		}
   556  	}
   557  }
   558  
   559  func TestBaseIndent(t *testing.T) {
   560  	t.Parallel()
   561  	// The testfile must not contain multi-line raw strings since those
   562  	// are not indented (because their values must not change) and make
   563  	// this test fail.
   564  	const filename = "printer.go"
   565  	src, err := os.ReadFile(filename)
   566  	if err != nil {
   567  		panic(err) // error in test
   568  	}
   569  
   570  	file, err := parser.ParseFile(fset, filename, src, 0)
   571  	if err != nil {
   572  		panic(err) // error in test
   573  	}
   574  
   575  	for indent := 0; indent < 4; indent++ {
   576  		indent := indent
   577  		t.Run(fmt.Sprint(indent), func(t *testing.T) {
   578  			t.Parallel()
   579  			var buf bytes.Buffer
   580  			(&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file)
   581  			// all code must be indented by at least 'indent' tabs
   582  			lines := bytes.Split(buf.Bytes(), []byte{'\n'})
   583  			for i, line := range lines {
   584  				if len(line) == 0 {
   585  					continue // empty lines don't have indentation
   586  				}
   587  				n := 0
   588  				for j, b := range line {
   589  					if b != '\t' {
   590  						// end of indentation
   591  						n = j
   592  						break
   593  					}
   594  				}
   595  				if n < indent {
   596  					t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line)
   597  				}
   598  			}
   599  		})
   600  	}
   601  }
   602  
   603  // TestFuncType tests that an ast.FuncType with a nil Params field
   604  // can be printed (per go/ast specification). Test case for issue 3870.
   605  func TestFuncType(t *testing.T) {
   606  	src := &ast.File{
   607  		Name: &ast.Ident{Name: "p"},
   608  		Decls: []ast.Decl{
   609  			&ast.FuncDecl{
   610  				Name: &ast.Ident{Name: "f"},
   611  				Type: &ast.FuncType{},
   612  			},
   613  		},
   614  	}
   615  
   616  	var buf bytes.Buffer
   617  	if err := Fprint(&buf, fset, src); err != nil {
   618  		t.Fatal(err)
   619  	}
   620  	got := buf.String()
   621  
   622  	const want = `package p
   623  
   624  func f()
   625  `
   626  
   627  	if got != want {
   628  		t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
   629  	}
   630  }
   631  
   632  type limitWriter struct {
   633  	remaining int
   634  	errCount  int
   635  }
   636  
   637  func (l *limitWriter) Write(buf []byte) (n int, err error) {
   638  	n = len(buf)
   639  	if n >= l.remaining {
   640  		n = l.remaining
   641  		err = io.EOF
   642  		l.errCount++
   643  	}
   644  	l.remaining -= n
   645  	return n, err
   646  }
   647  
   648  // Test whether the printer stops writing after the first error
   649  func TestWriteErrors(t *testing.T) {
   650  	t.Parallel()
   651  	const filename = "printer.go"
   652  	src, err := os.ReadFile(filename)
   653  	if err != nil {
   654  		panic(err) // error in test
   655  	}
   656  	file, err := parser.ParseFile(fset, filename, src, 0)
   657  	if err != nil {
   658  		panic(err) // error in test
   659  	}
   660  	for i := 0; i < 20; i++ {
   661  		lw := &limitWriter{remaining: i}
   662  		err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file)
   663  		if lw.errCount > 1 {
   664  			t.Fatal("Writes continued after first error returned")
   665  		}
   666  		// We expect errCount be 1 iff err is set
   667  		if (lw.errCount != 0) != (err != nil) {
   668  			t.Fatal("Expected err when errCount != 0")
   669  		}
   670  	}
   671  }
   672  
   673  // TextX is a skeleton test that can be filled in for debugging one-off cases.
   674  // Do not remove.
   675  func TestX(t *testing.T) {
   676  	const src = `
   677  package p
   678  func _() {}
   679  `
   680  	_, err := format([]byte(src), 0)
   681  	if err != nil {
   682  		t.Error(err)
   683  	}
   684  }
   685  
   686  func TestCommentedNode(t *testing.T) {
   687  	const (
   688  		input = `package main
   689  
   690  func foo() {
   691  	// comment inside func
   692  }
   693  
   694  // leading comment
   695  type bar int // comment2
   696  
   697  `
   698  
   699  		foo = `func foo() {
   700  	// comment inside func
   701  }`
   702  
   703  		bar = `// leading comment
   704  type bar int	// comment2
   705  `
   706  	)
   707  
   708  	fset := token.NewFileSet()
   709  	f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments)
   710  	if err != nil {
   711  		t.Fatal(err)
   712  	}
   713  
   714  	var buf bytes.Buffer
   715  
   716  	err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments})
   717  	if err != nil {
   718  		t.Fatal(err)
   719  	}
   720  
   721  	if buf.String() != foo {
   722  		t.Errorf("got %q, want %q", buf.String(), foo)
   723  	}
   724  
   725  	buf.Reset()
   726  
   727  	err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments})
   728  	if err != nil {
   729  		t.Fatal(err)
   730  	}
   731  
   732  	if buf.String() != bar {
   733  		t.Errorf("got %q, want %q", buf.String(), bar)
   734  	}
   735  }
   736  
   737  func TestIssue11151(t *testing.T) {
   738  	const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n"
   739  	fset := token.NewFileSet()
   740  	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
   741  	if err != nil {
   742  		t.Fatal(err)
   743  	}
   744  
   745  	var buf bytes.Buffer
   746  	Fprint(&buf, fset, f)
   747  	got := buf.String()
   748  	const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n" // \r following opening /* should be stripped
   749  	if got != want {
   750  		t.Errorf("\ngot : %q\nwant: %q", got, want)
   751  	}
   752  
   753  	// the resulting program must be valid
   754  	_, err = parser.ParseFile(fset, "", got, 0)
   755  	if err != nil {
   756  		t.Errorf("%v\norig: %q\ngot : %q", err, src, got)
   757  	}
   758  }
   759  
   760  // If a declaration has multiple specifications, a parenthesized
   761  // declaration must be printed even if Lparen is token.NoPos.
   762  func TestParenthesizedDecl(t *testing.T) {
   763  	// a package with multiple specs in a single declaration
   764  	const src = "package p; var ( a float64; b int )"
   765  	fset := token.NewFileSet()
   766  	f, err := parser.ParseFile(fset, "", src, 0)
   767  	if err != nil {
   768  		t.Fatal(err)
   769  	}
   770  
   771  	// print the original package
   772  	var buf bytes.Buffer
   773  	err = Fprint(&buf, fset, f)
   774  	if err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	original := buf.String()
   778  
   779  	// now remove parentheses from the declaration
   780  	for i := 0; i != len(f.Decls); i++ {
   781  		f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos
   782  	}
   783  	buf.Reset()
   784  	err = Fprint(&buf, fset, f)
   785  	if err != nil {
   786  		t.Fatal(err)
   787  	}
   788  	noparen := buf.String()
   789  
   790  	if noparen != original {
   791  		t.Errorf("got %q, want %q", noparen, original)
   792  	}
   793  }
   794  
   795  // Verify that we don't print a newline between "return" and its results, as
   796  // that would incorrectly cause a naked return.
   797  func TestIssue32854(t *testing.T) {
   798  	src := `package foo
   799  
   800  func f() {
   801          return Composite{
   802                  call(),
   803          }
   804  }`
   805  	fset := token.NewFileSet()
   806  	file, err := parser.ParseFile(fset, "", src, 0)
   807  	if err != nil {
   808  		panic(err)
   809  	}
   810  
   811  	// Replace the result with call(), which is on the next line.
   812  	fd := file.Decls[0].(*ast.FuncDecl)
   813  	ret := fd.Body.List[0].(*ast.ReturnStmt)
   814  	ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0]
   815  
   816  	var buf bytes.Buffer
   817  	if err := Fprint(&buf, fset, ret); err != nil {
   818  		t.Fatal(err)
   819  	}
   820  	want := "return call()"
   821  	if got := buf.String(); got != want {
   822  		t.Fatalf("got %q, want %q", got, want)
   823  	}
   824  }
   825  

View as plain text