Skip to content

Commit

Permalink
Comments should contain leading, trailing, and detached
Browse files Browse the repository at this point in the history
  • Loading branch information
pseudomuto committed Feb 21, 2018
1 parent cb3ae78 commit ac19067
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ env:
- PATH="${PROTOC_TARGET}/bin:${PATH}"

go:
- 1.9.x
- 1.10.x
- master

cache:
Expand Down
68 changes: 49 additions & 19 deletions comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,67 @@ package protokit
import (
"github.com/golang/protobuf/protoc-gen-go/descriptor"

"bytes"
"strconv"
"strings"
)

// A Comment describes the leading, trailing, and detached comments for a proto object. See `SourceCodeInfo_Location` in
// descriptor.proto for details on what those terms mean
type Comment struct {
Leading string
Trailing string
Detached []string
}

// String returns the leading and trailing comments joined by 2 line breaks (`\n\n`). If either are empty, the line
// breaks are removed.
func (c *Comment) String() string {
b := new(bytes.Buffer)
if c.GetLeading() != "" {
b.WriteString(c.GetLeading())
b.WriteString("\n\n")
}

b.WriteString(c.GetTrailing())

return strings.TrimSpace(b.String())
}

func newComment(loc *descriptor.SourceCodeInfo_Location) *Comment {
detached := make([]string, len(loc.GetLeadingDetachedComments()))
for i, c := range loc.GetLeadingDetachedComments() {
detached[i] = scrub(c)
}

return &Comment{
Leading: scrub(loc.GetLeadingComments()),
Trailing: scrub(loc.GetTrailingComments()),
Detached: detached,
}
}

// GetLeading returns the leading comments
func (c *Comment) GetLeading() string { return c.Leading }

// GetTrailing returns the leading comments
func (c *Comment) GetTrailing() string { return c.Trailing }

// GetDetached returns the detached leading comments
func (c *Comment) GetDetached() []string { return c.Detached }

// Comments is a map of source location paths to values.
//
// The values are a concatenation of the leader and trailing comments (null delimited). If either are missing, they are
// simply removed so that the null terminator isn't included.
type Comments map[string]string
type Comments map[string]*Comment

// ParseComments parses all comments within a proto file. The locations are encoded into the map by joining the paths
// with a "." character. E.g. `4.2.3.0`.
//
// Leading spaces are trimmed for each distinct value (leading, trailing) before joinging with `\x00`.
// Leading/trailing spaces are trimmed for each comment type (leading, trailing, detached)
func ParseComments(fd *descriptor.FileDescriptorProto) Comments {
comments := make(Comments)

for _, loc := range fd.GetSourceCodeInfo().GetLocation() {
leading := loc.GetLeadingComments()
trailing := loc.GetTrailingComments()

if leading == "" && trailing == "" {
if loc.GetLeadingComments() == "" && loc.GetTrailingComments() == "" && len(loc.GetLeadingDetachedComments()) == 0 {
continue
}

Expand All @@ -34,16 +73,7 @@ func ParseComments(fd *descriptor.FileDescriptorProto) Comments {
key[idx] = strconv.Itoa(int(p))
}

parts := make([]string, 0, 2)
if leading != "" {
parts = append(parts, scrub(leading))
}

if trailing != "" {
parts = append(parts, scrub(trailing))
}

comments[strings.Join(key, ".")] = strings.Join(parts, "\x00")
comments[strings.Join(key, ".")] = newComment(loc)
}

return comments
Expand Down
13 changes: 8 additions & 5 deletions comments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ func (assert *CommentsTest) SetupSuite() {

func (assert *CommentsTest) TestComments() {
tests := []struct {
key string
value string
key string
leading string
trailing string
}{
{"6.0.2.1", "Add an item to your list\n\nAdds a new item to the specified list."}, // leading commend
{"4.0.2.0", "The id of the list."}, // tailing comment
{"6.0.2.1", "Add an item to your list\n\nAdds a new item to the specified list.", ""}, // leading commend
{"4.0.2.0", "", "The id of the list."}, // tailing comment
}

for _, test := range tests {
assert.Equal(test.value, assert.comments[test.key])
assert.Equal(test.leading, assert.comments[test.key].GetLeading())
assert.Equal(test.trailing, assert.comments[test.key].GetTrailing())
assert.Len(assert.comments[test.key].GetDetached(), 0)
}
}
2 changes: 1 addition & 1 deletion examples/jsonator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func newFile(fd *protokit.FileDescriptor) *file {

return &file{
Name: fmt.Sprintf("%s.%s", fd.GetPackage(), fd.GetName()),
Description: fd.GetDescription(),
Description: fd.GetComments().String(),
Services: svcs,
}
}
Expand Down
18 changes: 9 additions & 9 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func ParseFile(fd *descriptor.FileDescriptorProto) *FileDescriptor {
file := &FileDescriptor{
comments: comments,
FileDescriptorProto: fd,
Description: comments[fmt.Sprintf("%d", packageCommentPath)],
Comments: comments[fmt.Sprintf("%d", packageCommentPath)],
}

ctx := ContextWithFileDescriptor(context.Background(), file)
Expand Down Expand Up @@ -80,7 +80,7 @@ func parseEnums(ctx context.Context, protos []*descriptor.EnumDescriptorProto) [
enums[i] = &EnumDescriptor{
common: newCommon(file, commentPath, longName),
EnumDescriptorProto: ed,
Description: file.comments[commentPath],
Comments: file.comments[commentPath],
Parent: parent,
}

Expand All @@ -102,8 +102,8 @@ func parseEnumValues(ctx context.Context, protos []*descriptor.EnumValueDescript
values[i] = &EnumValueDescriptor{
common: newCommon(file, "", longName),
EnumValueDescriptorProto: vd,
Enum: enum,
Description: file.comments[fmt.Sprintf("%s.%d.%d", enum.path, enumValueCommentPath, i)],
Enum: enum,
Comments: file.comments[fmt.Sprintf("%s.%d.%d", enum.path, enumValueCommentPath, i)],
}
}

Expand Down Expand Up @@ -131,7 +131,7 @@ func parseExtensions(ctx context.Context, protos []*descriptor.FieldDescriptorPr
exts[i] = &ExtensionDescriptor{
common: newCommon(file, commentPath, longName),
FieldDescriptorProto: ext,
Description: file.comments[commentPath],
Comments: file.comments[commentPath],
Parent: parent,
}
}
Expand All @@ -156,7 +156,7 @@ func parseMessages(ctx context.Context, protos []*descriptor.DescriptorProto) []
msgs[i] = &Descriptor{
common: newCommon(file, commentPath, longName),
DescriptorProto: md,
Description: file.comments[commentPath],
Comments: file.comments[commentPath],
Parent: parent,
}

Expand All @@ -181,7 +181,7 @@ func parseMessageFields(ctx context.Context, protos []*descriptor.FieldDescripto
fields[i] = &FieldDescriptor{
common: newCommon(file, "", longName),
FieldDescriptorProto: fd,
Description: file.comments[fmt.Sprintf("%s.%d.%d", message.path, messageFieldCommentPath, i)],
Comments: file.comments[fmt.Sprintf("%s.%d.%d", message.path, messageFieldCommentPath, i)],
Message: message,
}
}
Expand All @@ -200,7 +200,7 @@ func parseServices(ctx context.Context, protos []*descriptor.ServiceDescriptorPr
svcs[i] = &ServiceDescriptor{
common: newCommon(file, commentPath, longName),
ServiceDescriptorProto: sd,
Description: file.comments[commentPath],
Comments: file.comments[commentPath],
}

svcCtx := ContextWithServiceDescriptor(ctx, svcs[i])
Expand All @@ -223,7 +223,7 @@ func parseServiceMethods(ctx context.Context, protos []*descriptor.MethodDescrip
common: newCommon(file, "", longName),
MethodDescriptorProto: md,
Service: svc,
Description: file.comments[fmt.Sprintf("%s.%d.%d", svc.path, serviceMethodCommentPath, i)],
Comments: file.comments[fmt.Sprintf("%s.%d.%d", svc.path, serviceMethodCommentPath, i)],
}
}

Expand Down
30 changes: 15 additions & 15 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (assert *ParserTest) TestParseCodeGenRequest() {
func (assert *ParserTest) TestParseFile() {
file := protokit.ParseFile(proto3)
assert.True(file.IsProto3())
assert.Contains(file.GetDescription(), "The official documentation for the Todo API.\n\n")
assert.Contains(file.GetComments().String(), "The official documentation for the Todo API.\n\n")
assert.Len(file.GetExtensions(), 0) // no extensions in proto3

file = protokit.ParseFile(proto2)
Expand All @@ -64,13 +64,13 @@ func (assert *ParserTest) TestFileEnums() {
assert.True(enum.IsProto3())
assert.Nil(enum.GetParent())
assert.NotNil(enum.GetFile())
assert.Equal("An enumeration of list types", enum.GetDescription())
assert.Equal("An enumeration of list types", enum.GetComments().String())
assert.Equal("com.pseudomuto.protokit.v1", enum.GetPackage())
assert.Len(enum.GetValues(), 2)

assert.Equal("REMINDERS", enum.GetValues()[0].GetName())
assert.Equal(enum, enum.GetValues()[0].GetEnum())
assert.Equal("The reminders type.", enum.GetNamedValue("REMINDERS").GetDescription())
assert.Equal("The reminders type.", enum.GetNamedValue("REMINDERS").GetComments().String())

assert.Nil(enum.GetNamedValue("whodis"))
}
Expand All @@ -82,7 +82,7 @@ func (assert *ParserTest) TestFileExtensions() {
assert.Equal("country", ext.GetName())
assert.Equal("BookingStatus.country", ext.GetLongName())
assert.Equal("com.pseudomuto.protokit.v1.BookingStatus.country", ext.GetFullName())
assert.Equal("The country the booking occurred in.", ext.GetDescription())
assert.Equal("The country the booking occurred in.", ext.GetComments().String())
}

func (assert *ParserTest) TestServices() {
Expand All @@ -95,7 +95,7 @@ func (assert *ParserTest) TestServices() {
assert.Equal("com.pseudomuto.protokit.v1.Todo", svc.GetFullName())
assert.NotNil(svc.GetFile())
assert.True(svc.IsProto3())
assert.Contains(svc.GetDescription(), "A service for managing \"todo\" items.\n\n")
assert.Contains(svc.GetComments().String(), "A service for managing \"todo\" items.\n\n")
assert.Equal("com.pseudomuto.protokit.v1", svc.GetPackage())
assert.Len(svc.GetMethods(), 2)

Expand All @@ -105,10 +105,10 @@ func (assert *ParserTest) TestServices() {
assert.Equal("com.pseudomuto.protokit.v1.Todo.CreateList", m.GetFullName())
assert.NotNil(m.GetFile())
assert.Equal(svc, m.GetService())
assert.Equal("Create a new todo list", m.GetDescription())
assert.Equal("Create a new todo list", m.GetComments().String())

m = svc.GetNamedMethod("Todo.AddItem")
assert.Equal("Add an item to your list\n\nAdds a new item to the specified list.", m.GetDescription())
assert.Equal("Add an item to your list\n\nAdds a new item to the specified list.", m.GetComments().String())

assert.Nil(svc.GetNamedMethod("wat"))
}
Expand All @@ -124,7 +124,7 @@ func (assert *ParserTest) TestFileMessages() {
assert.Equal("com.pseudomuto.protokit.v1.AddItemRequest", m.GetFullName())
assert.NotNil(m.GetFile())
assert.Nil(m.GetParent())
assert.Equal("A request message for adding new items.", m.GetDescription())
assert.Equal("A request message for adding new items.", m.GetComments().String())
assert.Equal("com.pseudomuto.protokit.v1", m.GetPackage())
assert.Len(m.GetMessageFields(), 3)
assert.Nil(m.GetMessageField("swingandamiss"))
Expand All @@ -138,7 +138,7 @@ func (assert *ParserTest) TestFileMessages() {
assert.Equal("com.pseudomuto.protokit.v1.AddItemRequest.completed", f.GetFullName())
assert.NotNil(f.GetFile())
assert.Equal(m, f.GetMessage())
assert.Equal("Whether or not the item is completed.", f.GetDescription())
assert.Equal("Whether or not the item is completed.", f.GetComments().String())

// just making sure google.protobuf.Any fields aren't special
m = file.GetMessage("List")
Expand All @@ -152,7 +152,7 @@ func (assert *ParserTest) TestFileMessages() {
m = file.GetMessage("Booking")
assert.NotNil(m.GetMessageField("reference_num"))
assert.NotNil(m.GetMessageField("reference_tag"))
assert.Equal("the numeric reference number", m.GetMessageField("reference_num").GetDescription())
assert.Equal("the numeric reference number", m.GetMessageField("reference_num").GetComments().String())
}

func (assert *ParserTest) TestMessageEnums() {
Expand All @@ -168,14 +168,14 @@ func (assert *ParserTest) TestMessageEnums() {
assert.NotNil(e.GetFile())
assert.Equal(m, e.GetParent())
assert.Equal(e, m.GetEnum("Item.Status"))
assert.Equal("An enumeration of possible statuses", e.GetDescription())
assert.Equal("An enumeration of possible statuses", e.GetComments().String())
assert.Len(e.GetValues(), 2)

val := e.GetNamedValue("COMPLETED")
assert.Equal("COMPLETED", val.GetName())
assert.Equal("Item.Status.COMPLETED", val.GetLongName())
assert.Equal("com.pseudomuto.protokit.v1.Item.Status.COMPLETED", val.GetFullName())
assert.Equal("The completed status.", val.GetDescription())
assert.Equal("The completed status.", val.GetComments().String())
assert.NotNil(val.GetFile())
}

Expand All @@ -187,7 +187,7 @@ func (assert *ParserTest) TestMessageExtensions() {
assert.Equal("optional_field_1", ext.GetName())
assert.Equal("BookingStatus.optional_field_1", ext.GetLongName())
assert.Equal("com.pseudomuto.protokit.v1.BookingStatus.optional_field_1", ext.GetFullName())
assert.Equal("An optional field to be used however you please.", ext.GetDescription())
assert.Equal("An optional field to be used however you please.", ext.GetComments().String())
}

func (assert *ParserTest) TestNestedMessages() {
Expand All @@ -204,13 +204,13 @@ func (assert *ParserTest) TestNestedMessages() {
assert.Equal("Status", n.GetName())
assert.Equal("CreateListResponse.Status", n.GetLongName())
assert.Equal("com.pseudomuto.protokit.v1.CreateListResponse.Status", n.GetFullName())
assert.Equal("An internal status message", n.GetDescription())
assert.Equal("An internal status message", n.GetComments().String())
assert.NotNil(n.GetFile())
assert.Equal(m, n.GetParent())

f := n.GetMessageField("code")
assert.Equal("CreateListResponse.Status.code", f.GetLongName())
assert.Equal("com.pseudomuto.protokit.v1.CreateListResponse.Status.code", f.GetFullName())
assert.NotNil(f.GetFile())
assert.Equal("The status code.", f.GetDescription())
assert.Equal("The status code.", f.GetComments().String())
}
Loading

0 comments on commit ac19067

Please sign in to comment.