summaryrefslogtreecommitdiff
path: root/src/pkg/go
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2012-06-14 13:23:46 +0200
committerOndřej Surý <ondrej@sury.org>2012-06-14 13:23:46 +0200
commit917c5fb8ec48e22459d77e3849e6d388f93d3260 (patch)
tree9c23734a6ffd4d2a8ac99502eda3cc812a8b130b /src/pkg/go
parent0003ee229fd33ff46cb5f2fe1e35f5c0284debc4 (diff)
downloadgolang-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.go14
-rw-r--r--src/pkg/go/ast/ast_test.go50
-rw-r--r--src/pkg/go/build/build.go4
-rw-r--r--src/pkg/go/parser/parser.go42
-rw-r--r--src/pkg/go/parser/parser_test.go195
-rw-r--r--src/pkg/go/printer/nodes.go15
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