diff options
Diffstat (limited to 'src/pkg/go/printer')
-rw-r--r-- | src/pkg/go/printer/nodes.go | 164 | ||||
-rw-r--r-- | src/pkg/go/printer/performance_test.go | 4 | ||||
-rw-r--r-- | src/pkg/go/printer/printer.go | 92 | ||||
-rw-r--r-- | src/pkg/go/printer/printer_test.go | 271 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments.golden | 11 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments.input | 10 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments2.golden | 79 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments2.input | 79 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.golden | 54 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.input | 54 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.golden | 15 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.input | 15 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.raw | 15 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.golden | 108 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.input | 86 |
15 files changed, 898 insertions, 159 deletions
diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index f13f9a5a8..ee0bbf1ed 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -83,7 +83,7 @@ func (p *printer) setComment(g *ast.CommentGroup) { // don't overwrite any pending comment in the p.comment cache // (there may be a pending comment when a line comment is // immediately followed by a lead comment with no other - // tokens inbetween) + // tokens between) if p.commentOffset == infinity { p.nextComment() // get comment ready for use } @@ -203,7 +203,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp } else { const r = 4 // threshold ratio := float64(size) / float64(prevSize) - useFF = ratio <= 1/r || r <= ratio + useFF = ratio <= 1.0/r || r <= ratio } } @@ -307,7 +307,7 @@ func (p *printer) parameters(fields *ast.FieldList) { p.print(blank) } // parameter type - p.expr(par.Type) + p.expr(stripParensAlways(par.Type)) prevLine = parLineEnd } // if the closing ")" is on a separate line from the last parameter, @@ -325,13 +325,18 @@ func (p *printer) parameters(fields *ast.FieldList) { } func (p *printer) signature(params, result *ast.FieldList) { - p.parameters(params) + if params != nil { + p.parameters(params) + } else { + p.print(token.LPAREN, token.RPAREN) + } n := result.NumFields() if n > 0 { + // result != nil p.print(blank) if n == 1 && result.List[0].Names == nil { // single anonymous result; no ()'s - p.expr(result.List[0].Type) + p.expr(stripParensAlways(result.List[0].Type)) return } p.parameters(result) @@ -725,7 +730,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { case *ast.FuncLit: p.expr(x.Type) - p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true) + p.adjBlock(p.distanceFrom(x.Type.Pos()), blank, x.Body) case *ast.ParenExpr: if _, hasParens := x.X.(*ast.ParenExpr); hasParens { @@ -786,7 +791,14 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { if len(x.Args) > 1 { depth++ } - p.expr1(x.Fun, token.HighestPrec, depth) + if _, ok := x.Fun.(*ast.FuncType); ok { + // conversions to literal function types require parentheses around the type + p.print(token.LPAREN) + p.expr1(x.Fun, token.HighestPrec, depth) + p.print(token.RPAREN) + } else { + p.expr1(x.Fun, token.HighestPrec, depth) + } p.print(x.Lparen, token.LPAREN) if x.Ellipsis.IsValid() { p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis) @@ -848,9 +860,9 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { case ast.SEND | ast.RECV: p.print(token.CHAN) case ast.RECV: - p.print(token.ARROW, token.CHAN) + p.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same case ast.SEND: - p.print(token.CHAN, token.ARROW) + p.print(token.CHAN, x.Arrow, token.ARROW) } p.print(blank) p.expr(x.Value) @@ -877,30 +889,38 @@ func (p *printer) expr(x ast.Expr) { // Print the statement list indented, but without a newline after the last statement. // Extra line breaks between statements in the source are respected but at most one // empty line is printed between statements. -func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) { - // TODO(gri): fix _indent code - if _indent > 0 { +func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { + if nindent > 0 { p.print(indent) } multiLine := false - for i, s := range list { - // _indent == 0 only for lists of switch/select case clauses; - // in those cases each clause is a new section - p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || _indent == 0 || multiLine) - p.stmt(s, nextIsRBrace && i == len(list)-1) - multiLine = p.isMultiLine(s) - } - if _indent > 0 { + i := 0 + for _, s := range list { + // ignore empty statements (was issue 3466) + if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { + // _indent == 0 only for lists of switch/select case clauses; + // in those cases each clause is a new section + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) + p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || multiLine) + } + p.stmt(s, nextIsRBrace && i == len(list)-1) + multiLine = p.isMultiLine(s) + i++ + } + } + if nindent > 0 { p.print(unindent) } } // block prints an *ast.BlockStmt; it always spans at least two lines. -func (p *printer) block(s *ast.BlockStmt, indent int) { - p.print(s.Pos(), token.LBRACE) - p.stmtList(s.List, indent, true) - p.linebreak(p.lineFor(s.Rbrace), 1, ignore, true) - p.print(s.Rbrace, token.RBRACE) +func (p *printer) block(b *ast.BlockStmt, nindent int) { + p.print(b.Lbrace, token.LBRACE) + p.stmtList(b.List, nindent, true) + p.linebreak(p.lineFor(b.Rbrace), 1, ignore, true) + p.print(b.Rbrace, token.RBRACE) } func isTypeName(x ast.Expr) bool { @@ -939,6 +959,13 @@ func stripParens(x ast.Expr) ast.Expr { return x } +func stripParensAlways(x ast.Expr) ast.Expr { + if x, ok := x.(*ast.ParenExpr); ok { + return stripParensAlways(x.X) + } + return x +} + func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) { p.print(blank) needsBlank := false @@ -1405,19 +1432,19 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) { return } -func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool { +// bodySize is like nodeSize but it is specialized for *ast.BlockStmt's. +func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int { pos1 := b.Pos() pos2 := b.Rbrace if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) { // opening and closing brace are on different lines - don't make it a one-liner - return false + return maxSize + 1 } if len(b.List) > 5 || p.commentBefore(p.posFor(pos2)) { // too many statements or there is a comment inside - don't make it a one-liner - return false + return maxSize + 1 } // otherwise, estimate body size - const maxSize = 100 bodySize := 0 for i, s := range b.List { if i > 0 { @@ -1425,19 +1452,23 @@ func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool { } bodySize += p.nodeSize(s, maxSize) } - return headerSize+bodySize <= maxSize + return bodySize } -func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool) { +// adjBlock prints an "adjacent" block (e.g., a for-loop or function body) following +// a header (e.g., a for-loop control clause or function signature) of given headerSize. +// If the header's and block's size are "small enough" and the block is "simple enough", +// the block is printed on the current line, without line breaks, spaced from the header +// by sep. Otherwise the block's opening "{" is printed on the current line, followed by +// lines for the block's statements and its closing "}". +// +func (p *printer) adjBlock(headerSize int, sep whiteSpace, b *ast.BlockStmt) { if b == nil { return } - if p.isOneLineFunc(b, headerSize) { - sep := vtab - if isLit { - sep = blank - } + const maxSize = 100 + if headerSize+p.bodySize(b, maxSize) <= maxSize { p.print(sep, b.Lbrace, token.LBRACE) if len(b.List) > 0 { p.print(blank) @@ -1453,17 +1484,20 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool) { return } - p.print(blank) + if sep != ignore { + p.print(blank) // always use blank + } p.block(b, 1) } -// distance returns the column difference between from and to if both -// are on the same line; if they are on different lines (or unknown) -// the result is infinity. -func (p *printer) distance(from0 token.Pos, to token.Position) int { - from := p.posFor(from0) - if from.IsValid() && to.IsValid() && from.Line == to.Line { - return to.Column - from.Column +// distanceFrom returns the column difference between from and p.pos (the current +// estimated position) if both are on the same line; if they are on different lines +// (or unknown) the result is infinity. +func (p *printer) distanceFrom(from token.Pos) int { + if from.IsValid() && p.pos.IsValid() { + if f := p.posFor(from); f.Line == p.pos.Line { + return p.pos.Column - f.Column + } } return infinity } @@ -1477,7 +1511,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl) { } p.expr(d.Name) p.signature(d.Type.Params, d.Type.Results) - p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false) + p.adjBlock(p.distanceFrom(d.Pos()), vtab, d.Body) } func (p *printer) decl(decl ast.Decl) { @@ -1507,31 +1541,35 @@ func declToken(decl ast.Decl) (tok token.Token) { return } -func (p *printer) file(src *ast.File) { - p.setComment(src.Doc) - p.print(src.Pos(), token.PACKAGE, blank) - p.expr(src.Name) - - if len(src.Decls) > 0 { - tok := token.ILLEGAL - for _, d := range src.Decls { - prev := tok - tok = declToken(d) - // if the declaration token changed (e.g., from CONST to TYPE) - // or the next declaration has documentation associated with it, - // print an empty line between top-level declarations - // (because p.linebreak is called with the position of d, which - // is past any documentation, the minimum requirement is satisfied - // even w/o the extra getDoc(d) nil-check - leave it in case the - // linebreak logic improves - there's already a TODO). +func (p *printer) declList(list []ast.Decl) { + tok := token.ILLEGAL + for _, d := range list { + prev := tok + tok = declToken(d) + // If the declaration token changed (e.g., from CONST to TYPE) + // or the next declaration has documentation associated with it, + // print an empty line between top-level declarations. + // (because p.linebreak is called with the position of d, which + // is past any documentation, the minimum requirement is satisfied + // even w/o the extra getDoc(d) nil-check - leave it in case the + // linebreak logic improves - there's already a TODO). + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) min := 1 if prev != tok || getDoc(d) != nil { min = 2 } p.linebreak(p.lineFor(d.Pos()), min, ignore, false) - p.decl(d) } + p.decl(d) } +} +func (p *printer) file(src *ast.File) { + p.setComment(src.Doc) + p.print(src.Pos(), token.PACKAGE, blank) + p.expr(src.Name) + p.declList(src.Decls) p.print(newline) } diff --git a/src/pkg/go/printer/performance_test.go b/src/pkg/go/printer/performance_test.go index 0c6a4e71f..5b29affcb 100644 --- a/src/pkg/go/printer/performance_test.go +++ b/src/pkg/go/printer/performance_test.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // This file implements a simple printer performance benchmark: -// go test -bench=BenchmarkPrint +// go test -bench=BenchmarkPrint package printer @@ -20,7 +20,7 @@ import ( var testfile *ast.File func testprint(out io.Writer, file *ast.File) { - if err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil { + if err := (&Config{TabIndent | UseSpaces, 8, 0}).Fprint(out, fset, file); err != nil { log.Fatalf("print error: %s", err) } } diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go index a027d32da..3c8d23e65 100644 --- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "text/tabwriter" + "unicode" ) const ( @@ -164,15 +165,15 @@ func (p *printer) atLineBegin(pos token.Position) { // write indentation // use "hard" htabs - indentation columns // must not be discarded by the tabwriter - for i := 0; i < p.indent; i++ { + n := p.Config.Indent + p.indent // include base indentation + for i := 0; i < n; i++ { p.output = append(p.output, '\t') } // update positions - i := p.indent - p.pos.Offset += i - p.pos.Column += i - p.out.Column += i + p.pos.Offset += n + p.pos.Column += n + p.out.Column += n } // writeByte writes ch n times to p.output and updates p.pos. @@ -220,14 +221,6 @@ func (p *printer) writeString(pos token.Position, s string, isLit bool) { // atLineBegin updates p.pos if there's indentation, but p.pos // is the position of s. p.pos = pos - // reset state if the file changed - // (used when printing merged ASTs of different files - // e.g., the result of ast.MergePackageFiles) - if p.last.IsValid() && p.last.Filename != pos.Filename { - p.indent = 0 - p.mode = 0 - p.wsbuf = p.wsbuf[0:0] - } } if isLit { @@ -405,6 +398,7 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as // Split comment text into lines // (using strings.Split(text, "\n") is significantly slower for // this specific purpose, as measured with: go test -bench=Print) +// func split(text string) []string { // count lines (comment text never ends in a newline) n := 1 @@ -432,6 +426,7 @@ func split(text string) []string { // Returns true if s contains only white space // (only tabs and blanks can appear in the printer's context). +// func isBlank(s string) bool { for i := 0; i < len(s); i++ { if s[i] > ' ' { @@ -441,6 +436,7 @@ func isBlank(s string) bool { return true } +// commonPrefix returns the common prefix of a and b. func commonPrefix(a, b string) string { i := 0 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { @@ -449,11 +445,22 @@ func commonPrefix(a, b string) string { return a[0:i] } +// trimRight returns s with trailing whitespace removed. +func trimRight(s string) string { + return strings.TrimRightFunc(s, unicode.IsSpace) +} + +// stripCommonPrefix removes a common prefix from /*-style comment lines (unless no +// comment line is indented, all but the first line have some form of space prefix). +// The prefix is computed using heuristics such that is likely that the comment +// contents are nicely laid out after re-printing each line using the printer's +// current indentation. +// func stripCommonPrefix(lines []string) { - if len(lines) < 2 { + if len(lines) <= 1 { return // at most one line - nothing to do } - // len(lines) >= 2 + // len(lines) > 1 // The heuristic in this function tries to handle a few // common patterns of /*-style comments: Comments where @@ -479,7 +486,7 @@ func stripCommonPrefix(lines []string) { for i, line := range lines[1 : len(lines)-1] { switch { case isBlank(line): - lines[1+i] = "" // range starts at line 1 + lines[1+i] = "" // range starts with lines[1] case first: prefix = commonPrefix(line, line) first = false @@ -544,9 +551,7 @@ func stripCommonPrefix(lines []string) { } // Shorten the computed common prefix by the length of // suffix, if it is found as suffix of the prefix. - if strings.HasSuffix(prefix, string(suffix)) { - prefix = prefix[0 : len(prefix)-len(suffix)] - } + prefix = strings.TrimSuffix(prefix, string(suffix)) } } @@ -570,9 +575,9 @@ func stripCommonPrefix(lines []string) { } // Remove the common prefix from all but the first and empty lines. - for i, line := range lines[1:] { - if len(line) != 0 { - lines[1+i] = line[len(prefix):] // range starts at line 1 + for i, line := range lines { + if i > 0 && line != "" { + lines[i] = line[len(prefix):] } } } @@ -605,13 +610,26 @@ func (p *printer) writeComment(comment *ast.Comment) { // shortcut common case of //-style comments if text[1] == '/' { - p.writeString(pos, text, true) + p.writeString(pos, trimRight(text), true) return } // for /*-style comments, print line by line and let the // write function take care of the proper indentation lines := split(text) + + // The comment started in the first column but is going + // to be indented. For an idempotent result, add indentation + // to all lines such that they look like they were indented + // before - this will make sure the common prefix computation + // is the same independent of how many times formatting is + // applied (was issue 1835). + if pos.IsValid() && pos.Column == 1 && p.indent > 0 { + for i, line := range lines[1:] { + lines[1+i] = " " + line + } + } + stripCommonPrefix(lines) // write comment lines, separated by formfeed, @@ -622,7 +640,7 @@ func (p *printer) writeComment(comment *ast.Comment) { pos = p.pos } if len(line) > 0 { - p.writeString(pos, line, true) + p.writeString(pos, trimRight(line), true) } } } @@ -1012,9 +1030,9 @@ func (p *printer) printNode(node interface{}) error { case ast.Expr: p.expr(n) case ast.Stmt: - // A labeled statement will un-indent to position the - // label. Set indent to 1 so we don't get indent "underflow". - if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt { + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + if _, ok := n.(*ast.LabeledStmt); ok { p.indent = 1 } p.stmt(n, false) @@ -1022,6 +1040,17 @@ func (p *printer) printNode(node interface{}) error { p.decl(n) case ast.Spec: p.spec(n, 1, false) + case []ast.Stmt: + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + for _, s := range n { + if _, ok := s.(*ast.LabeledStmt); ok { + p.indent = 1 + } + } + p.stmtList(n, 0, false) + case []ast.Decl: + p.declList(n) case *ast.File: p.file(n) default: @@ -1140,7 +1169,7 @@ func (p *trimmer) Write(data []byte) (n int, err error) { // ---------------------------------------------------------------------------- // Public interface -// A Mode value is a set of flags (or 0). They coontrol printing. +// A Mode value is a set of flags (or 0). They control printing. type Mode uint const ( @@ -1154,6 +1183,7 @@ const ( type Config struct { Mode Mode // default: 0 Tabwidth int // default: 8 + Indent int // default: 0 (all code is indented at least by this much) } // fprint implements Fprint and takes a nodesSizes map for setting up the printer state. @@ -1198,7 +1228,7 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{ } // flush tabwriter, if any - if tw, _ := (output).(*tabwriter.Writer); tw != nil { + if tw, _ := output.(*tabwriter.Writer); tw != nil { err = tw.Flush() } @@ -1215,8 +1245,8 @@ type CommentedNode struct { // Fprint "pretty-prints" an AST node to output for a given configuration cfg. // Position information is interpreted relative to the file set fset. -// The node type must be *ast.File, *CommentedNode, or assignment-compatible -// to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. +// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, +// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. // func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { return cfg.fprint(output, fset, node, make(map[ast.Node]int)) diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go index 497d671f2..8454ac12b 100644 --- a/src/pkg/go/printer/printer_test.go +++ b/src/pkg/go/printer/printer_test.go @@ -6,7 +6,9 @@ package printer import ( "bytes" + "errors" "flag" + "fmt" "go/ast" "go/parser" "go/token" @@ -25,33 +27,28 @@ var update = flag.Bool("update", false, "update golden files") var fset = token.NewFileSet() -func lineString(text []byte, i int) string { - i0 := i - for i < len(text) && text[i] != '\n' { - i++ - } - return string(text[i0:i]) -} - type checkMode uint const ( export checkMode = 1 << iota rawFormat + idempotent ) -func runcheck(t *testing.T, source, golden string, mode checkMode) { - // parse source - prog, err := parser.ParseFile(fset, source, nil, parser.ParseComments) +// format parses src, prints the corresponding AST, verifies the resulting +// src is syntactically correct, and returns the resulting src or an error +// if any. +func format(src []byte, mode checkMode) ([]byte, error) { + // parse src + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { - t.Error(err) - return + return nil, fmt.Errorf("parse: %s\n%s", err, src) } // filter exports if necessary if mode&export != 0 { - ast.FileExports(prog) // ignore result - prog.Comments = nil // don't print comments that are not in AST + ast.FileExports(f) // ignore result + f.Comments = nil // don't print comments that are not in AST } // determine printer configuration @@ -60,17 +57,72 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) { cfg.Mode |= RawFormat } - // format source + // print AST var buf bytes.Buffer - if err := cfg.Fprint(&buf, fset, prog); err != nil { - t.Error(err) + if err := cfg.Fprint(&buf, fset, f); err != nil { + return nil, fmt.Errorf("print: %s", err) } - res := buf.Bytes() - // formatted source must be valid + // make sure formated output is syntactically correct + res := buf.Bytes() if _, err := parser.ParseFile(fset, "", res, 0); err != nil { + return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) + } + + return res, nil +} + +// lineAt returns the line in text starting at offset offs. +func lineAt(text []byte, offs int) []byte { + i := offs + for i < len(text) && text[i] != '\n' { + i++ + } + return text[offs:i] +} + +// diff compares a and b. +func diff(aname, bname string, a, b []byte) error { + var buf bytes.Buffer // holding long error message + + // compare lengths + if len(a) != len(b) { + fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b)) + } + + // compare contents + line := 1 + offs := 1 + for i := 0; i < len(a) && i < len(b); i++ { + ch := a[i] + if ch != b[i] { + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs)) + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs)) + fmt.Fprintf(&buf, "\n\n") + break + } + if ch == '\n' { + line++ + offs = i + 1 + } + } + + if buf.Len() > 0 { + return errors.New(buf.String()) + } + return nil +} + +func runcheck(t *testing.T, source, golden string, mode checkMode) { + src, err := ioutil.ReadFile(source) + if err != nil { + t.Error(err) + return + } + + res, err := format(src, mode) + if err != nil { t.Error(err) - t.Logf("\n%s", res) return } @@ -89,23 +141,19 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) { return } - // compare lengths - if len(res) != len(gld) { - t.Errorf("len = %d, expected %d (= len(%s))", len(res), len(gld), golden) + // formatted source and golden must be the same + if err := diff(source, golden, res, gld); err != nil { + t.Error(err) + return } - // compare contents - for i, line, offs := 0, 1, 0; i < len(res) && i < len(gld); i++ { - ch := res[i] - if ch != gld[i] { - t.Errorf("%s:%d:%d: %s", source, line, i-offs+1, lineString(res, offs)) - t.Errorf("%s:%d:%d: %s", golden, line, i-offs+1, lineString(gld, offs)) - t.Error() - return - } - if ch == '\n' { - line++ - offs = i + 1 + if mode&idempotent != 0 { + // formatting golden must be idempotent + // (This is very difficult to achieve in general and for now + // it is only checked for files explicitly marked as such.) + res, err = format(gld, mode) + if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil { + t.Errorf("golden is not idempotent: %s", err) } } } @@ -142,15 +190,16 @@ type entry struct { // Use go test -update to create/update the respective golden files. var data = []entry{ - {"empty.input", "empty.golden", 0}, + {"empty.input", "empty.golden", idempotent}, {"comments.input", "comments.golden", 0}, {"comments.input", "comments.x", export}, - {"linebreaks.input", "linebreaks.golden", 0}, - {"expressions.input", "expressions.golden", 0}, - {"expressions.input", "expressions.raw", rawFormat}, + {"comments2.input", "comments2.golden", idempotent}, + {"linebreaks.input", "linebreaks.golden", idempotent}, + {"expressions.input", "expressions.golden", idempotent}, + {"expressions.input", "expressions.raw", rawFormat | idempotent}, {"declarations.input", "declarations.golden", 0}, {"statements.input", "statements.golden", 0}, - {"slow.input", "slow.golden", 0}, + {"slow.input", "slow.golden", idempotent}, } func TestFiles(t *testing.T) { @@ -248,7 +297,7 @@ func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { } } -// Verify that the printer produces always produces a correct program +// Verify that the printer produces a correct program // even if the position information of comments introducing newlines // is incorrect. func TestBadComments(t *testing.T) { @@ -385,28 +434,136 @@ func (t *t) foo(a, b, c int) int { } } -// TextX is a skeleton test that can be filled in for debugging one-off cases. -// Do not remove. -func TestX(t *testing.T) { - const src = ` -package p -func _() {} -` - // parse original - f, err := parser.ParseFile(fset, "src", src, parser.ParseComments) +var decls = []string{ + `import "fmt"`, + "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi", + "func sum(x, y int) int\t{ return x + y }", +} + +func TestDeclLists(t *testing.T) { + for _, src := range decls { + file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls) // only print declarations + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +var stmts = []string{ + "i := 0", + "select {}\nvar a, b = 1, 2\nreturn a + b", + "go f()\ndefer func() {}()", +} + +func TestStmtLists(t *testing.T) { + for _, src := range stmts { + file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +func TestBaseIndent(t *testing.T) { + // The testfile must not contain multi-line raw strings since those + // are not indented (because their values must not change) and make + // this test fail. + const filename = "printer.go" + src, err := ioutil.ReadFile(filename) if err != nil { - t.Fatal(err) + panic(err) // error in test + } + + file, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + for indent := 0; indent < 4; indent++ { + buf.Reset() + (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file) + // all code must be indented by at least 'indent' tabs + lines := bytes.Split(buf.Bytes(), []byte{'\n'}) + for i, line := range lines { + if len(line) == 0 { + continue // empty lines don't have indentation + } + n := 0 + for j, b := range line { + if b != '\t' { + // end of indentation + n = j + break + } + } + if n < indent { + t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line) + } + } + } +} + +// TestFuncType tests that an ast.FuncType with a nil Params field +// can be printed (per go/ast specification). Test case for issue 3870. +func TestFuncType(t *testing.T) { + src := &ast.File{ + Name: &ast.Ident{Name: "p"}, + Decls: []ast.Decl{ + &ast.FuncDecl{ + Name: &ast.Ident{Name: "f"}, + Type: &ast.FuncType{}, + }, + }, } - // pretty-print original var buf bytes.Buffer - if err = (&Config{Mode: UseSpaces, Tabwidth: 8}).Fprint(&buf, fset, f); err != nil { + if err := Fprint(&buf, fset, src); err != nil { t.Fatal(err) } + got := buf.String() - // parse pretty printed original - if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { - t.Fatalf("%s\n%s", err, buf.Bytes()) + const want = `package p + +func f() +` + + if got != want { + t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) } +} +// TextX is a skeleton test that can be filled in for debugging one-off cases. +// Do not remove. +func TestX(t *testing.T) { + const src = ` +package p +func _() {} +` + _, err := format([]byte(src), 0) + if err != nil { + t.Error(err) + } } diff --git a/src/pkg/go/printer/testdata/comments.golden b/src/pkg/go/printer/testdata/comments.golden index d9aa2d82f..610a42a68 100644 --- a/src/pkg/go/printer/testdata/comments.golden +++ b/src/pkg/go/printer/testdata/comments.golden @@ -529,7 +529,7 @@ func _() { } func _() { - var a = []int{1, 2}// jasldf + var a = []int{1, 2}// jasldf _ = a } @@ -626,4 +626,13 @@ func _() { var lflag bool // -l - disable line directives } +// Trailing white space in comments should be trimmed +func _() { + // This comment has 4 blanks following that should be trimmed: + /* Each line of this comment has blanks or tabs following that should be trimmed: + line 2: + line 3: + */ +} + /* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/src/pkg/go/printer/testdata/comments.input b/src/pkg/go/printer/testdata/comments.input index 6084b3fe4..d121dd4be 100644 --- a/src/pkg/go/printer/testdata/comments.input +++ b/src/pkg/go/printer/testdata/comments.input @@ -534,7 +534,7 @@ func _() { } func _() { - var a = []int{1, 2, // jasldf + var a = []int{1, 2, // jasldf } _ = a } @@ -630,5 +630,13 @@ var vflag string // -v [y.output] - y.output file var lflag bool // -l - disable line directives } +// Trailing white space in comments should be trimmed +func _() { +// This comment has 4 blanks following that should be trimmed: +/* Each line of this comment has blanks or tabs following that should be trimmed: + line 2: + line 3: +*/ +} /* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/src/pkg/go/printer/testdata/comments2.golden b/src/pkg/go/printer/testdata/comments2.golden new file mode 100644 index 000000000..d3b50bf3e --- /dev/null +++ b/src/pkg/go/printer/testdata/comments2.golden @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is a package for testing comment placement by go/printer. +// +package main + +// Test cases for idempotent comment formatting (was issue 1835). +/* +c1a +*/ +/* + c1b +*/ +/* foo +c1c +*/ +/* foo + c1d +*/ +/* +c1e +foo */ +/* + c1f + foo */ + +func f() { + /* + c2a + */ + /* + c2b + */ + /* foo + c2c + */ + /* foo + c2d + */ + /* + c2e + foo */ + /* + c2f + foo */ +} + +func g() { + /* + c3a + */ + /* + c3b + */ + /* foo + c3c + */ + /* foo + c3d + */ + /* + c3e + foo */ + /* + c3f + foo */ +} + +// Test case taken literally from issue 1835. +func main() { + /* + prints test 5 times + */ + for i := 0; i < 5; i++ { + println("test") + } +} diff --git a/src/pkg/go/printer/testdata/comments2.input b/src/pkg/go/printer/testdata/comments2.input new file mode 100644 index 000000000..6f8c85c94 --- /dev/null +++ b/src/pkg/go/printer/testdata/comments2.input @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is a package for testing comment placement by go/printer. +// +package main + +// Test cases for idempotent comment formatting (was issue 1835). +/* +c1a +*/ +/* + c1b +*/ +/* foo +c1c +*/ +/* foo + c1d +*/ +/* +c1e +foo */ +/* + c1f + foo */ + +func f() { +/* +c2a +*/ +/* + c2b +*/ +/* foo +c2c +*/ +/* foo + c2d +*/ +/* +c2e +foo */ +/* + c2f + foo */ +} + +func g() { +/* +c3a +*/ +/* + c3b +*/ +/* foo +c3c +*/ +/* foo + c3d +*/ +/* +c3e +foo */ +/* + c3f + foo */ +} + +// Test case taken literally from issue 1835. +func main() { +/* +prints test 5 times +*/ + for i := 0; i < 5; i++ { + println("test") + } +}
\ No newline at end of file diff --git a/src/pkg/go/printer/testdata/declarations.golden b/src/pkg/go/printer/testdata/declarations.golden index 71ed32ed1..0ad72d349 100644 --- a/src/pkg/go/printer/testdata/declarations.golden +++ b/src/pkg/go/printer/testdata/declarations.golden @@ -654,6 +654,35 @@ var _ = map[int]int{ abcde: a, // align with previous line } +// alignment of map composite entries: test cases from issue 3965 +// aligned +var _ = T1{ + a: x, + b: y, + cccccccccccccccccccc: z, +} + +// not aligned +var _ = T2{ + a: x, + b: y, + ccccccccccccccccccccc: z, +} + +// aligned +var _ = T3{ + aaaaaaaaaaaaaaaaaaaa: x, + b: y, + c: z, +} + +// not aligned +var _ = T4{ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: x, + b: y, + c: z, +} + func _() { var _ = T{ a, // must introduce trailing comma @@ -858,3 +887,28 @@ type _ interface { r string, x ...int) } + +// omit superfluous parentheses in parameter lists +func _(int) +func _(int) +func _(x int) +func _(x int) +func _(x, y int) +func _(x, y int) + +func _() int +func _() int +func _() int + +func _() (x int) +func _() (x int) +func _() (x int) + +// special cases: some channel types require parentheses +func _(x chan (<-chan int)) +func _(x chan (<-chan int)) +func _(x chan (<-chan int)) + +func _(x chan<- (chan int)) +func _(x chan<- (chan int)) +func _(x chan<- (chan int)) diff --git a/src/pkg/go/printer/testdata/declarations.input b/src/pkg/go/printer/testdata/declarations.input index d74cff25d..455c0c6c1 100644 --- a/src/pkg/go/printer/testdata/declarations.input +++ b/src/pkg/go/printer/testdata/declarations.input @@ -667,6 +667,35 @@ var _ = map[int]int{ abcde: a, // align with previous line } +// alignment of map composite entries: test cases from issue 3965 +// aligned +var _ = T1{ + a: x, + b: y, + cccccccccccccccccccc: z, +} + +// not aligned +var _ = T2{ + a: x, + b: y, + ccccccccccccccccccccc: z, +} + +// aligned +var _ = T3{ + aaaaaaaaaaaaaaaaaaaa: x, + b: y, + c: z, +} + +// not aligned +var _ = T4{ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: x, + b: y, + c: z, +} + func _() { var _ = T{ @@ -867,3 +896,28 @@ p, q, r string, x ...int) } + +// omit superfluous parentheses in parameter lists +func _((int)) +func _((((((int)))))) +func _(x (int)) +func _(x (((((int)))))) +func _(x, y (int)) +func _(x, y (((((int)))))) + +func _() (int) +func _() ((int)) +func _() ((((((int)))))) + +func _() (x int) +func _() (x (int)) +func _() (x (((((int)))))) + +// special cases: some channel types require parentheses +func _(x chan(<-chan int)) +func _(x (chan(<-chan int))) +func _(x ((((chan(<-chan int)))))) + +func _(x chan<-(chan int)) +func _(x (chan<-(chan int))) +func _(x ((((chan<-(chan int)))))) diff --git a/src/pkg/go/printer/testdata/expressions.golden b/src/pkg/go/printer/testdata/expressions.golden index 45fa4d97a..4291c557c 100644 --- a/src/pkg/go/printer/testdata/expressions.golden +++ b/src/pkg/go/printer/testdata/expressions.golden @@ -647,3 +647,18 @@ func _() { a..., ) } + +// Literal function types in conversions must be parenthesized; +// for now go/parser accepts the unparenthesized form where it +// is non-ambiguous. +func _() { + // these conversions should be rewritten to look + // the same as the parenthesized conversions below + _ = (func())(nil) + _ = (func(x int) float)(nil) + _ = (func() func() func())(nil) + + _ = (func())(nil) + _ = (func(x int) float)(nil) + _ = (func() func() func())(nil) +} diff --git a/src/pkg/go/printer/testdata/expressions.input b/src/pkg/go/printer/testdata/expressions.input index f545c6605..1ec12a050 100644 --- a/src/pkg/go/printer/testdata/expressions.input +++ b/src/pkg/go/printer/testdata/expressions.input @@ -676,3 +676,18 @@ func _() { a..., ) } + +// Literal function types in conversions must be parenthesized; +// for now go/parser accepts the unparenthesized form where it +// is non-ambiguous. +func _() { + // these conversions should be rewritten to look + // the same as the parenthesized conversions below + _ = func()()(nil) + _ = func(x int)(float)(nil) + _ = func() func() func()()(nil) + + _ = (func()())(nil) + _ = (func(x int)(float))(nil) + _ = (func() func() func()())(nil) +} diff --git a/src/pkg/go/printer/testdata/expressions.raw b/src/pkg/go/printer/testdata/expressions.raw index 87a4b0083..062900e07 100644 --- a/src/pkg/go/printer/testdata/expressions.raw +++ b/src/pkg/go/printer/testdata/expressions.raw @@ -647,3 +647,18 @@ func _() { a..., ) } + +// Literal function types in conversions must be parenthesized; +// for now go/parser accepts the unparenthesized form where it +// is non-ambiguous. +func _() { + // these conversions should be rewritten to look + // the same as the parenthesized conversions below + _ = (func())(nil) + _ = (func(x int) float)(nil) + _ = (func() func() func())(nil) + + _ = (func())(nil) + _ = (func(x int) float)(nil) + _ = (func() func() func())(nil) +} diff --git a/src/pkg/go/printer/testdata/statements.golden b/src/pkg/go/printer/testdata/statements.golden index 4d70617bf..3b298f95e 100644 --- a/src/pkg/go/printer/testdata/statements.golden +++ b/src/pkg/go/printer/testdata/statements.golden @@ -241,7 +241,7 @@ func _() { } } -// Formatting of for-statement headers. +// Formatting of for-statement headers for single-line for-loops. func _() { for { } @@ -279,6 +279,86 @@ func _() { } // no parens printed } +// Formatting of for-statement headers for multi-line for-loops. +func _() { + for { + } + for expr { + } + for expr { + } // no parens printed + for { + } // no semicolons printed + for x := expr; ; { + use(x) + } + for expr { + } // no semicolons printed + for expr { + } // no semicolons and parens printed + for ; ; expr = false { + } + for x := expr; expr; { + use(x) + } + for x := expr; ; expr = false { + use(x) + } + for ; expr; expr = false { + } + for x := expr; expr; expr = false { + use(x) + } + for x := range []int{} { + use(x) + } + for x := range []int{} { + use(x) + } // no parens printed +} + +// Formatting of selected short single- and multi-line statements. +func _() { + if cond { + } + if cond { + } // multiple lines + if cond { + } else { + } // else clause always requires multiple lines + + for { + } + for i := 0; i < len(a); 1++ { + } + for i := 0; i < len(a); 1++ { + a[i] = i + } + for i := 0; i < len(a); 1++ { + a[i] = i + } // multiple lines + + for i := range a { + } + for i := range a { + a[i] = i + } + for i := range a { + a[i] = i + } // multiple lines + + go func() { + for { + a <- <-b + } + }() + defer func() { + if x := recover(); x != nil { + err = fmt.Sprintf("error: %s", x.msg) + } + }() +} + // Don't remove mandatory parentheses around composite literals in control clauses. func _() { // strip parentheses - no composite literals or composite literals don't start with a type name @@ -527,3 +607,29 @@ AVeryLongLabelThatShouldNotAffectFormatting: // There should be a single empty line before this comment. MoreCode() } + +// Formatting of empty statements. +func _() { + +} + +func _() { +} + +func _() { +} + +func _() { + f() +} + +func _() { +L: + ; +} + +func _() { +L: + ; + f() +} diff --git a/src/pkg/go/printer/testdata/statements.input b/src/pkg/go/printer/testdata/statements.input index bd03bc98b..e7fcc0e54 100644 --- a/src/pkg/go/printer/testdata/statements.input +++ b/src/pkg/go/printer/testdata/statements.input @@ -223,7 +223,7 @@ func _() { } -// Formatting of for-statement headers. +// Formatting of for-statement headers for single-line for-loops. func _() { for{} for expr {} @@ -235,14 +235,70 @@ func _() { for; ; expr = false {} for x :=expr; expr; {use(x)} for x := expr;; expr=false {use(x)} - for;expr;expr =false { - } + for;expr;expr =false {} for x := expr;expr;expr = false { use(x) } for x := range []int{} { use(x) } for x := range (([]int{})) { use(x) } // no parens printed } +// Formatting of for-statement headers for multi-line for-loops. +func _() { + for{ + } + for expr { + } + for (expr) { + } // no parens printed + for;;{ + } // no semicolons printed + for x :=expr;; {use( x) + } + for; expr;{ + } // no semicolons printed + for; ((expr));{ + } // no semicolons and parens printed + for; ; expr = false { + } + for x :=expr; expr; {use(x) + } + for x := expr;; expr=false {use(x) + } + for;expr;expr =false { + } + for x := expr;expr;expr = false { + use(x) + } + for x := range []int{} { + use(x) } + for x := range (([]int{})) { + use(x) } // no parens printed +} + + +// Formatting of selected short single- and multi-line statements. +func _() { + if cond {} + if cond { + } // multiple lines + if cond {} else {} // else clause always requires multiple lines + + for {} + for i := 0; i < len(a); 1++ {} + for i := 0; i < len(a); 1++ { a[i] = i } + for i := 0; i < len(a); 1++ { a[i] = i + } // multiple lines + + for i := range a {} + for i := range a { a[i] = i } + for i := range a { a[i] = i + } // multiple lines + + go func() { for { a <- <-b } }() + defer func() { if x := recover(); x != nil { err = fmt.Sprintf("error: %s", x.msg) } }() +} + + // Don't remove mandatory parentheses around composite literals in control clauses. func _() { // strip parentheses - no composite literals or composite literals don't start with a type name @@ -468,3 +524,27 @@ AVeryLongLabelThatShouldNotAffectFormatting: // There should be a single empty line before this comment. MoreCode() } + + +// Formatting of empty statements. +func _() { + ;;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() {;;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() {;;;;;;;;;;;;;;;;;;;;;;;;;} + +func _() { +f();;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() { +L:;;;;;;;;;;;; +} + +func _() { +L:;;;;;;;;;;;; + f() +} |