diff options
author | Ondřej Surý <ondrej@sury.org> | 2012-06-14 13:23:46 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2012-06-14 13:23:46 +0200 |
commit | 917c5fb8ec48e22459d77e3849e6d388f93d3260 (patch) | |
tree | 9c23734a6ffd4d2a8ac99502eda3cc812a8b130b /src/pkg/go | |
parent | 0003ee229fd33ff46cb5f2fe1e35f5c0284debc4 (diff) | |
download | golang-917c5fb8ec48e22459d77e3849e6d388f93d3260.tar.gz |
Imported Upstream version 1.0.2upstream/1.0.2
Diffstat (limited to 'src/pkg/go')
-rw-r--r-- | src/pkg/go/ast/ast.go | 14 | ||||
-rw-r--r-- | src/pkg/go/ast/ast_test.go | 50 | ||||
-rw-r--r-- | src/pkg/go/build/build.go | 4 | ||||
-rw-r--r-- | src/pkg/go/parser/parser.go | 42 | ||||
-rw-r--r-- | src/pkg/go/parser/parser_test.go | 195 | ||||
-rw-r--r-- | src/pkg/go/printer/nodes.go | 15 |
6 files changed, 286 insertions, 34 deletions
diff --git a/src/pkg/go/ast/ast.go b/src/pkg/go/ast/ast.go index 7123fe58f..d2e75dc1c 100644 --- a/src/pkg/go/ast/ast.go +++ b/src/pkg/go/ast/ast.go @@ -87,8 +87,12 @@ func stripTrailingWhitespace(s string) string { return s[0:i] } -// Text returns the text of the comment, -// with the comment markers - //, /*, and */ - removed. +// Text returns the text of the comment. +// Comment markers (//, /*, and */), the first space of a line comment, and +// leading and trailing empty lines are removed. Multiple empty lines are +// reduced to one, and trailing space on lines is trimmed. Unless the result +// is empty, it is newline-terminated. +// func (g *CommentGroup) Text() string { if g == nil { return "" @@ -104,11 +108,9 @@ func (g *CommentGroup) Text() string { // The parser has given us exactly the comment text. switch c[1] { case '/': - //-style comment + //-style comment (no newline at the end) c = c[2:] - // Remove leading space after //, if there is one. - // TODO(gri) This appears to be necessary in isolated - // cases (bignum.RatFromString) - why? + // strip first space - required for Example tests if len(c) > 0 && c[0] == ' ' { c = c[1:] } diff --git a/src/pkg/go/ast/ast_test.go b/src/pkg/go/ast/ast_test.go new file mode 100644 index 000000000..1a6a283f2 --- /dev/null +++ b/src/pkg/go/ast/ast_test.go @@ -0,0 +1,50 @@ +// 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. + +package ast + +import ( + "testing" +) + +var comments = []struct { + list []string + text string +}{ + {[]string{"//"}, ""}, + {[]string{"// "}, ""}, + {[]string{"//", "//", "// "}, ""}, + {[]string{"// foo "}, "foo\n"}, + {[]string{"//", "//", "// foo"}, "foo\n"}, + {[]string{"// foo bar "}, "foo bar\n"}, + {[]string{"// foo", "// bar"}, "foo\nbar\n"}, + {[]string{"// foo", "//", "//", "//", "// bar"}, "foo\n\nbar\n"}, + {[]string{"// foo", "/* bar */"}, "foo\n bar\n"}, + {[]string{"//", "//", "//", "// foo", "//", "//", "//"}, "foo\n"}, + + {[]string{"/**/"}, ""}, + {[]string{"/* */"}, ""}, + {[]string{"/**/", "/**/", "/* */"}, ""}, + {[]string{"/* Foo */"}, " Foo\n"}, + {[]string{"/* Foo Bar */"}, " Foo Bar\n"}, + {[]string{"/* Foo*/", "/* Bar*/"}, " Foo\n Bar\n"}, + {[]string{"/* Foo*/", "/**/", "/**/", "/**/", "// Bar"}, " Foo\n\nBar\n"}, + {[]string{"/* Foo*/", "/*\n*/", "//", "/*\n*/", "// Bar"}, " Foo\n\nBar\n"}, + {[]string{"/* Foo*/", "// Bar"}, " Foo\nBar\n"}, + {[]string{"/* Foo\n Bar*/"}, " Foo\n Bar\n"}, +} + +func TestCommentText(t *testing.T) { + for i, c := range comments { + list := make([]*Comment, len(c.list)) + for i, s := range c.list { + list[i] = &Comment{Text: s} + } + + text := (&CommentGroup{list}).Text() + if text != c.text { + t.Errorf("case %d: got %q; expected %q", i, text, c.text) + } + } +} diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go index d749aef15..7a81d5030 100644 --- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -68,7 +68,7 @@ type Context struct { // ReadDir returns a slice of os.FileInfo, sorted by Name, // describing the content of the named directory. - // If ReadDir is nil, Import uses io.ReadDir. + // If ReadDir is nil, Import uses ioutil.ReadDir. ReadDir func(dir string) (fi []os.FileInfo, err error) // OpenFile opens a file (not a directory) for reading. @@ -339,7 +339,7 @@ func (e *NoGoError) Error() string { // - files starting with _ or . (likely editor temporary files) // - files with build constraints not satisfied by the context // -// If an error occurs, Import returns a non-nil error also returns a non-nil +// If an error occurs, Import returns a non-nil error and a non-nil // *Package containing partial information. // func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) { diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go index e362e13a7..20e505d97 100644 --- a/src/pkg/go/parser/parser.go +++ b/src/pkg/go/parser/parser.go @@ -267,13 +267,13 @@ func (p *parser) consumeComment() (comment *ast.Comment, endline int) { // Consume a group of adjacent comments, add it to the parser's // comments list, and return it together with the line at which -// the last comment in the group ends. An empty line or non-comment -// token terminates a comment group. +// the last comment in the group ends. A non-comment token or n +// empty lines terminate a comment group. // -func (p *parser) consumeCommentGroup() (comments *ast.CommentGroup, endline int) { +func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { var list []*ast.Comment endline = p.file.Line(p.pos) - for p.tok == token.COMMENT && endline+1 >= p.file.Line(p.pos) { + for p.tok == token.COMMENT && p.file.Line(p.pos) <= endline+n { var comment *ast.Comment comment, endline = p.consumeComment() list = append(list, comment) @@ -314,7 +314,7 @@ func (p *parser) next() { if p.file.Line(p.pos) == line { // The comment is on same line as the previous token; it // cannot be a lead comment but may be a line comment. - comment, endline = p.consumeCommentGroup() + comment, endline = p.consumeCommentGroup(0) if p.file.Line(p.pos) != endline { // The next token is on a different line, thus // the last comment group is a line comment. @@ -325,7 +325,7 @@ func (p *parser) next() { // consume successor comments, if any endline = -1 for p.tok == token.COMMENT { - comment, endline = p.consumeCommentGroup() + comment, endline = p.consumeCommentGroup(1) } if endline+1 == p.file.Line(p.pos) { @@ -627,10 +627,10 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { doc := p.leadComment - // fields + // FieldDecl list, typ := p.parseVarList(false) - // optional tag + // Tag var tag *ast.BasicLit if p.tok == token.STRING { tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} @@ -645,7 +645,6 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { } else { // ["*"] TypeName (AnonymousField) typ = list[0] // we always have at least one element - p.resolve(typ) if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") @@ -657,6 +656,7 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment} p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) return field } @@ -699,12 +699,15 @@ func (p *parser) parsePointerType() *ast.StarExpr { return &ast.StarExpr{Star: star, X: base} } +// If the result is an identifier, it is not resolved. func (p *parser) tryVarType(isParam bool) ast.Expr { if isParam && p.tok == token.ELLIPSIS { pos := p.pos p.next() typ := p.tryIdentOrType(isParam) // don't use parseType so we can provide better error message - if typ == nil { + if typ != nil { + p.resolve(typ) + } else { p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{From: pos, To: p.pos} } @@ -713,6 +716,7 @@ func (p *parser) tryVarType(isParam bool) ast.Expr { return p.tryIdentOrType(false) } +// If the result is an identifier, it is not resolved. func (p *parser) parseVarType(isParam bool) ast.Expr { typ := p.tryVarType(isParam) if typ == nil { @@ -724,6 +728,7 @@ func (p *parser) parseVarType(isParam bool) ast.Expr { return typ } +// If any of the results are identifiers, they are not resolved. func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) { if p.trace { defer un(trace(p, "VarList")) @@ -744,9 +749,7 @@ func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) { } // if we had a list of identifiers, it must be followed by a type - if typ = p.tryVarType(isParam); typ != nil { - p.resolve(typ) - } + typ = p.tryVarType(isParam) return } @@ -756,7 +759,10 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ defer un(trace(p, "ParameterList")) } + // ParameterDecl list, typ := p.parseVarList(ellipsisOk) + + // analyze case if typ != nil { // IdentifierList Type idents := p.makeIdentList(list) @@ -765,10 +771,10 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ // Go spec: The scope of an identifier denoting a function // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) if p.tok == token.COMMA { p.next() } - for p.tok != token.RPAREN && p.tok != token.EOF { idents := p.parseIdentList() typ := p.parseVarType(ellipsisOk) @@ -777,18 +783,18 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ // Go spec: The scope of an identifier denoting a function // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) if !p.atComma("parameter list") { break } p.next() } - } else { // Type { "," Type } (anonymous parameters) params = make([]*ast.Field, len(list)) - for i, x := range list { - p.resolve(x) - params[i] = &ast.Field{Type: x} + for i, typ := range list { + p.resolve(typ) + params[i] = &ast.Field{Type: typ} } } diff --git a/src/pkg/go/parser/parser_test.go b/src/pkg/go/parser/parser_test.go index 5e45acd00..1b7a41b1b 100644 --- a/src/pkg/go/parser/parser_test.go +++ b/src/pkg/go/parser/parser_test.go @@ -5,10 +5,12 @@ package parser import ( + "bytes" "fmt" "go/ast" "go/token" "os" + "strings" "testing" ) @@ -25,7 +27,7 @@ func TestParse(t *testing.T) { for _, filename := range validFiles { _, err := ParseFile(fset, filename, nil, DeclarationErrors) if err != nil { - t.Errorf("ParseFile(%s): %v", filename, err) + t.Fatalf("ParseFile(%s): %v", filename, err) } } } @@ -70,7 +72,7 @@ func TestParseExpr(t *testing.T) { src := "a + b" x, err := ParseExpr(src) if err != nil { - t.Errorf("ParseExpr(%s): %v", src, err) + t.Fatalf("ParseExpr(%s): %v", src, err) } // sanity check if _, ok := x.(*ast.BinaryExpr); !ok { @@ -81,7 +83,7 @@ func TestParseExpr(t *testing.T) { src = "a + *" _, err = ParseExpr(src) if err == nil { - t.Errorf("ParseExpr(%s): %v", src, err) + t.Fatalf("ParseExpr(%s): %v", src, err) } // it must not crash @@ -93,7 +95,7 @@ func TestParseExpr(t *testing.T) { func TestColonEqualsScope(t *testing.T) { f, err := ParseFile(fset, "", `package p; func f() { x, y, z := x, y, z }`, 0) if err != nil { - t.Errorf("parse: %s", err) + t.Fatal(err) } // RHS refers to undefined globals; LHS does not. @@ -115,7 +117,7 @@ func TestColonEqualsScope(t *testing.T) { func TestVarScope(t *testing.T) { f, err := ParseFile(fset, "", `package p; func f() { var x, y, z = x, y, z }`, 0) if err != nil { - t.Errorf("parse: %s", err) + t.Fatal(err) } // RHS refers to undefined globals; LHS does not. @@ -133,6 +135,67 @@ func TestVarScope(t *testing.T) { } } +func TestUnresolved(t *testing.T) { + f, err := ParseFile(fset, "", ` +package p +// +func f1a(int) +func f2a(byte, int, float) +func f3a(a, b int, c float) +func f4a(...complex) +func f5a(a s1a, b ...complex) +// +func f1b(*int) +func f2b([]byte, (int), *float) +func f3b(a, b *int, c []float) +func f4b(...*complex) +func f5b(a s1a, b ...[]complex) +// +type s1a struct { int } +type s2a struct { byte; int; s1a } +type s3a struct { a, b int; c float } +// +type s1b struct { *int } +type s2b struct { byte; int; *float } +type s3b struct { a, b *s3b; c []float } +`, 0) + if err != nil { + t.Fatal(err) + } + + want := "int " + // f1a + "byte int float " + // f2a + "int float " + // f3a + "complex " + // f4a + "complex " + // f5a + // + "int " + // f1b + "byte int float " + // f2b + "int float " + // f3b + "complex " + // f4b + "complex " + // f5b + // + "int " + // s1a + "byte int " + // s2a + "int float " + // s3a + // + "int " + // s1a + "byte int float " + // s2a + "float " // s3a + + // collect unresolved identifiers + var buf bytes.Buffer + for _, u := range f.Unresolved { + buf.WriteString(u.Name) + buf.WriteByte(' ') + } + got := buf.String() + + if got != want { + t.Errorf("\ngot: %s\nwant: %s", got, want) + } +} + var imports = map[string]bool{ `"a"`: true, "`a`": true, @@ -177,3 +240,125 @@ func TestImports(t *testing.T) { } } } + +func TestCommentGroups(t *testing.T) { + f, err := ParseFile(fset, "", ` +package p /* 1a */ /* 1b */ /* 1c */ // 1d +/* 2a +*/ +// 2b +const pi = 3.1415 +/* 3a */ // 3b +/* 3c */ const e = 2.7182 + +// Example from issue 3139 +func ExampleCount() { + fmt.Println(strings.Count("cheese", "e")) + fmt.Println(strings.Count("five", "")) // before & after each rune + // Output: + // 3 + // 5 +} +`, ParseComments) + if err != nil { + t.Fatal(err) + } + expected := [][]string{ + {"/* 1a */", "/* 1b */", "/* 1c */", "// 1d"}, + {"/* 2a\n*/", "// 2b"}, + {"/* 3a */", "// 3b", "/* 3c */"}, + {"// Example from issue 3139"}, + {"// before & after each rune"}, + {"// Output:", "// 3", "// 5"}, + } + if len(f.Comments) != len(expected) { + t.Fatalf("got %d comment groups; expected %d", len(f.Comments), len(expected)) + } + for i, exp := range expected { + got := f.Comments[i].List + if len(got) != len(exp) { + t.Errorf("got %d comments in group %d; expected %d", len(got), i, len(exp)) + continue + } + for j, exp := range exp { + got := got[j].Text + if got != exp { + t.Errorf("got %q in group %d; expected %q", got, i, exp) + } + } + } +} + +func getField(file *ast.File, fieldname string) *ast.Field { + parts := strings.Split(fieldname, ".") + for _, d := range file.Decls { + if d, ok := d.(*ast.GenDecl); ok && d.Tok == token.TYPE { + for _, s := range d.Specs { + if s, ok := s.(*ast.TypeSpec); ok && s.Name.Name == parts[0] { + if s, ok := s.Type.(*ast.StructType); ok { + for _, f := range s.Fields.List { + for _, name := range f.Names { + if name.Name == parts[1] { + return f + } + } + } + } + } + } + } + } + return nil +} + +// Don't use ast.CommentGroup.Text() - we want to see exact comment text. +func commentText(c *ast.CommentGroup) string { + var buf bytes.Buffer + if c != nil { + for _, c := range c.List { + buf.WriteString(c.Text) + } + } + return buf.String() +} + +func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line string) { + f := getField(file, fieldname) + if f == nil { + t.Fatalf("field not found: %s", fieldname) + } + if got := commentText(f.Doc); got != lead { + t.Errorf("got lead comment %q; expected %q", got, lead) + } + if got := commentText(f.Comment); got != line { + t.Errorf("got line comment %q; expected %q", got, line) + } +} + +func TestLeadAndLineComments(t *testing.T) { + f, err := ParseFile(fset, "", ` +package p +type T struct { + /* F1 lead comment */ + // + F1 int /* F1 */ // line comment + // F2 lead + // comment + F2 int // F2 line comment + // f3 lead comment + f3 int // f3 line comment +} +`, ParseComments) + if err != nil { + t.Fatal(err) + } + checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") + checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") + checkFieldComments(t, f, "T.f3", "// f3 lead comment", "// f3 line comment") + ast.FileExports(f) + checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") + checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") + if getField(f, "T.f3") != nil { + t.Error("not expected to find T.f3") + } +} diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index 727d2a371..f13f9a5a8 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -60,8 +60,8 @@ func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (prin // setComment sets g as the next comment if g != nil and if node comments // are enabled - this mode is used when printing source code fragments such -// as exports only. It assumes that there are no other pending comments to -// intersperse. +// as exports only. It assumes that there is no pending comment in p.comments +// and at most one pending comment in the p.comment cache. func (p *printer) setComment(g *ast.CommentGroup) { if g == nil || !p.useNodeComments { return @@ -74,10 +74,19 @@ func (p *printer) setComment(g *ast.CommentGroup) { // should never happen - handle gracefully and flush // all comments up to g, ignore anything after that p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL) + p.comments = p.comments[0:1] + // in debug mode, report error + p.internalError("setComment found pending comments") } p.comments[0] = g p.cindex = 0 - p.nextComment() // get comment ready for use + // 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) + if p.commentOffset == infinity { + p.nextComment() // get comment ready for use + } } type exprListMode uint |