Repository: jhump/protoreflect
Pull request #475 (2022-01-05 03:15:54)

protoparse: trailing comments match protoc
Also updates the protoprint package to emit trailing comments correctly, to match conventions of protoc.

Previously, protoprint made some assumptions about how comments for block elements (those with braces `{}`, vs. compact elements that end in `;`) were represented. The [docs in `descriptor.proto`](https://github.com/protocolbuffers/protobuf/blob/84c2e1e5027ccce38cf7d79136471410d7d0fe93/src/google/protobuf/descriptor.proto#L834) only give examples for compact elements (fields to be precise), so I applied the same techniques to block elements:

```
message Example1 {
    // this is a leading comment for a field
    optional string name = 1;
    // this is the trailing comment

    optional uint64 id = 2; // this is also a trailing comment
}

// So I assumed this would be a leading comment for a message
message Example2 {
}
// and this would be a trailing comment for that message
```

However, my assumptions were incorrect. It turns out that `protoc` behaves rather differently for block comments:
```
// Yes, this is the leading comment for a message
message Example3 {
    // But THIS is the trailing comment for the message

} // not this
```

It treats the _open brace_ as the end of the message declaration, after which the trailing comment appears... not the close brace.

This PR updates `protoparse` (for parsing comments and computing the right `SourceCodeInfo`) as well as `protoprint` (for emitting source with the trailing comments in the right place).

jhump: Resolves https://github.com/bufbuild/buf/issues/798.

cc @amckinney

Patch:
diff --git a/desc/protoparse/source_code_info.go b/desc/protoparse/source_code_info.go
index 2104c59..02df689 100644
--- a/desc/protoparse/source_code_info.go
+++ b/desc/protoparse/source_code_info.go
@@ -105,18 +105,21 @@ func (r *parseResult) generateSourceCodeInfoForOption(sci *sourceCodeInfo, n *as
 }
 
 func (r *parseResult) generateSourceCodeInfoForMessage(sci *sourceCodeInfo, n ast.MessageDeclNode, fieldPath []int32, path []int32) {
-	sci.newLoc(n, path)
-
+	var openBrace ast.Node
 	var decls []ast.MessageElement
 	switch n := n.(type) {
 	case *ast.MessageNode:
+		openBrace = n.OpenBrace
 		decls = n.Decls
 	case *ast.GroupNode:
+		openBrace = n.OpenBrace
 		decls = n.Decls
 	case *ast.MapFieldNode:
+		sci.newLoc(n, path)
 		// map entry so nothing else to do
 		return
 	}
+	sci.newBlockLoc(n, openBrace, path)
 
 	sci.newLoc(n.MessageName(), append(path, internal.Message_nameTag))
 	// matching protoc, which emits the corresponding field type name (for group fields)
@@ -179,7 +182,7 @@ func (r *parseResult) generateSourceCodeInfoForMessage(sci *sourceCodeInfo, n as
 }
 
 func (r *parseResult) generateSourceCodeInfoForEnum(sci *sourceCodeInfo, n *ast.EnumNode, path []int32) {
-	sci.newLoc(n, path)
+	sci.newBlockLoc(n, n.OpenBrace, path)
 	sci.newLoc(n.Name, append(path, internal.Enum_nameTag))
 
 	var optIndex, valIndex, reservedNameIndex, reservedRangeIndex int32
@@ -238,7 +241,7 @@ func (r *parseResult) generateSourceCodeInfoForReservedRange(sci *sourceCodeInfo
 }
 
 func (r *parseResult) generateSourceCodeInfoForExtensions(sci *sourceCodeInfo, n *ast.ExtendNode, extendIndex, msgIndex *int32, extendPath, msgPath []int32) {
-	sci.newLoc(n, extendPath)
+	sci.newBlockLoc(n, n.OpenBrace, extendPath)
 	for _, decl := range n.Decls {
 		switch decl := decl.(type) {
 		case *ast.FieldNode:
@@ -255,7 +258,7 @@ func (r *parseResult) generateSourceCodeInfoForExtensions(sci *sourceCodeInfo, n
 }
 
 func (r *parseResult) generateSourceCodeInfoForOneOf(sci *sourceCodeInfo, n *ast.OneOfNode, fieldIndex, nestedMsgIndex *int32, fieldPath, nestedMsgPath, oneOfPath []int32) {
-	sci.newLoc(n, oneOfPath)
+	sci.newBlockLoc(n, n.OpenBrace, oneOfPath)
 	sci.newLoc(n.Name, append(oneOfPath, internal.OneOf_nameTag))
 
 	var optIndex int32
@@ -351,7 +354,7 @@ func (r *parseResult) generateSourceCodeInfoForExtensionRanges(sci *sourceCodeIn
 }
 
 func (r *parseResult) generateSourceCodeInfoForService(sci *sourceCodeInfo, n *ast.ServiceNode, path []int32) {
-	sci.newLoc(n, path)
+	sci.newBlockLoc(n, n.OpenBrace, path)
 	sci.newLoc(n.Name, append(path, internal.Service_nameTag))
 	var optIndex, rpcIndex int32
 	for _, child := range n.Decls {
@@ -366,7 +369,11 @@ func (r *parseResult) generateSourceCodeInfoForService(sci *sourceCodeInfo, n *a
 }
 
 func (r *parseResult) generateSourceCodeInfoForMethod(sci *sourceCodeInfo, n *ast.RPCNode, path []int32) {
-	sci.newLoc(n, path)
+	if n.OpenBrace != nil {
+		sci.newBlockLoc(n, n.OpenBrace, path)
+	} else {
+		sci.newLoc(n, path)
+	}
 	sci.newLoc(n.Name, append(path, internal.Method_nameTag))
 	if n.Input.Stream != nil {
 		sci.newLoc(n.Input.Stream, append(path, internal.Method_inputStreamTag))
@@ -400,9 +407,22 @@ func (sci *sourceCodeInfo) newLocWithoutComments(n ast.Node, path []int32) {
 	})
 }
 
+func (sci *sourceCodeInfo) newBlockLoc(n, openBrace ast.Node, path []int32) {
+	// Block definitions use trailing comments after the open brace "{" as the
+	// element's trailing comments. For example:
+	//
+	//    message Foo { // this is a trailing comment for a message
+	//
+	//    }             // not this
+	//
+	sci.newLocWithComments(n, n.LeadingComments(), openBrace.TrailingComments(), path)
+}
+
 func (sci *sourceCodeInfo) newLoc(n ast.Node, path []int32) {
-	leadingComments := n.LeadingComments()
-	trailingComments := n.TrailingComments()
+	sci.newLocWithComments(n, n.LeadingComments(), n.TrailingComments(), path)
+}
+
+func (sci *sourceCodeInfo) newLocWithComments(n ast.Node, leadingComments, trailingComments []ast.Comment, path []int32) {
 	if sci.commentUsed(leadingComments) {
 		leadingComments = nil
 	}
diff --git a/desc/protoprint/print.go b/desc/protoprint/print.go
index d4ce7f5..118fd1b 100644
--- a/desc/protoprint/print.go
+++ b/desc/protoprint/print.go
@@ -100,8 +100,7 @@ type Printer struct {
 	//    repeated string names = 1; // trailing comment
 	//
 	// If the trailing comment has more than one line, it will automatically be
-	// forced to the next line. Also, elements that end with "}" instead of ";"
-	// will have trailing comments rendered on the subsequent line.
+	// forced to the next line.
 	TrailingCommentsOnSeparateLine bool
 
 	// If true, the printed output will eschew any blank lines, which otherwise
@@ -720,13 +719,14 @@ func (p *Printer) typeString(fld *desc.FieldDescriptor, scope string) string {
 
 func (p *Printer) printMessage(md *desc.MessageDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
 	si := sourceInfo.Get(path)
-	p.printElement(true, si, w, indent, func(w *writer) {
+	p.printBlockElement(true, si, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 
 		fmt.Fprint(w, "message ")
 		nameSi := sourceInfo.Get(append(path, internal.Message_nameTag))
 		p.printElementString(nameSi, w, indent, md.GetName())
 		fmt.Fprintln(w, "{")
+		trailer(indent+1, true)
 
 		p.printMessageBody(md, mf, w, sourceInfo, path, indent+1)
 		p.indent(w, indent)
@@ -943,7 +943,7 @@ func (p *Printer) printField(fld *desc.FieldDescriptor, mf *dynamic.MessageFacto
 		si = sourceInfo.Get(path)
 	}
 
-	p.printElement(true, si, w, indent, func(w *writer) {
+	p.printBlockElement(true, si, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 		if shouldEmitLabel(fld) {
 			locSi := sourceInfo.Get(append(path, internal.Field_labelTag))
@@ -997,6 +997,8 @@ func (p *Printer) printField(fld *desc.FieldDescriptor, mf *dynamic.MessageFacto
 
 		if group {
 			fmt.Fprintln(w, "{")
+			trailer(indent+1, true)
+
 			p.printMessageBody(fld.GetMessageType(), mf, w, sourceInfo, groupPath, indent+1)
 
 			p.indent(w, indent)
@@ -1004,6 +1006,7 @@ func (p *Printer) printField(fld *desc.FieldDescriptor, mf *dynamic.MessageFacto
 
 		} else {
 			fmt.Fprint(w, ";")
+			trailer(indent, false)
 		}
 	})
 }
@@ -1033,14 +1036,15 @@ func isGroup(fld *desc.FieldDescriptor) bool {
 func (p *Printer) printOneOf(ood *desc.OneOfDescriptor, parentElements elementAddrs, startFieldIndex int, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int, ooIndex int32) {
 	oopath := append(parentPath, internal.Message_oneOfsTag, ooIndex)
 	oosi := sourceInfo.Get(oopath)
-	p.printElement(true, oosi, w, indent, func(w *writer) {
+	p.printBlockElement(true, oosi, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 		fmt.Fprint(w, "oneof ")
 		extNameSi := sourceInfo.Get(append(oopath, internal.OneOf_nameTag))
 		p.printElementString(extNameSi, w, indent, ood.GetName())
 		fmt.Fprintln(w, "{")
-
 		indent++
+		trailer(indent, true)
+
 		opts, err := p.extractOptions(ood, ood.GetOptions(), mf)
 		if err != nil {
 			if w.err == nil {
@@ -1100,6 +1104,11 @@ func (p *Printer) printExtensions(exts *extensionDecl, allExts extensions, paren
 	p.printElementString(extNameSi, w, indent, p.qualifyName(pkg, scope, exts.extendee))
 	fmt.Fprintln(w, "{")
 
+	if p.printTrailingComments(exts.sourceInfo, w, indent+1) && !p.Compact {
+		// separator line between trailing comment and next element
+		fmt.Fprintln(w)
+	}
+
 	count := len(exts.fields)
 	first := true
 	for idx := startFieldIndex; count > 0 && idx < len(parentElements.addrs); idx++ {
@@ -1122,11 +1131,6 @@ func (p *Printer) printExtensions(exts *extensionDecl, allExts extensions, paren
 
 	p.indent(w, indent)
 	fmt.Fprintln(w, "}")
-	p.printTrailingComments(exts.sourceInfo, w, indent)
-	if indent >= 0 && !w.newline {
-		// if we're not printing inline but element did not have trailing newline, add one now
-		fmt.Fprintln(w)
-	}
 }
 
 func (p *Printer) printExtensionRanges(parent *desc.MessageDescriptor, ranges []*descriptor.DescriptorProto_ExtensionRange, maxTag int32, addrs []elementAddr, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int) {
@@ -1210,15 +1214,16 @@ func (p *Printer) printReservedNames(names []string, addrs []elementAddr, w *wri
 
 func (p *Printer) printEnum(ed *desc.EnumDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
 	si := sourceInfo.Get(path)
-	p.printElement(true, si, w, indent, func(w *writer) {
+	p.printBlockElement(true, si, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 
 		fmt.Fprint(w, "enum ")
 		nameSi := sourceInfo.Get(append(path, internal.Enum_nameTag))
 		p.printElementString(nameSi, w, indent, ed.GetName())
 		fmt.Fprintln(w, "{")
-
 		indent++
+		trailer(indent, true)
+
 		opts, err := p.extractOptions(ed, ed.GetOptions(), mf)
 		if err != nil {
 			if w.err == nil {
@@ -1322,15 +1327,15 @@ func (p *Printer) printEnumValue(evd *desc.EnumValueDescriptor, mf *dynamic.Mess
 
 func (p *Printer) printService(sd *desc.ServiceDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
 	si := sourceInfo.Get(path)
-	p.printElement(true, si, w, indent, func(w *writer) {
+	p.printBlockElement(true, si, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 
 		fmt.Fprint(w, "service ")
 		nameSi := sourceInfo.Get(append(path, internal.Service_nameTag))
 		p.printElementString(nameSi, w, indent, sd.GetName())
 		fmt.Fprintln(w, "{")
-
 		indent++
+		trailer(indent, true)
 
 		opts, err := p.extractOptions(sd, sd.GetOptions(), mf)
 		if err != nil {
@@ -1371,7 +1376,7 @@ func (p *Printer) printService(sd *desc.ServiceDescriptor, mf *dynamic.MessageFa
 func (p *Printer) printMethod(mtd *desc.MethodDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
 	si := sourceInfo.Get(path)
 	pkg := mtd.GetFile().GetPackage()
-	p.printElement(true, si, w, indent, func(w *writer) {
+	p.printBlockElement(true, si, w, indent, func(w *writer, trailer func(int, bool)) {
 		p.indent(w, indent)
 
 		fmt.Fprint(w, "rpc ")
@@ -1407,6 +1412,7 @@ func (p *Printer) printMethod(mtd *desc.MethodDescriptor, mf *dynamic.MessageFac
 		if len(opts) > 0 {
 			fmt.Fprintln(w, "{")
 			indent++
+			trailer(indent, true)
 
 			elements := elementAddrs{dsc: mtd, opts: opts}
 			elements.addrs = optionsAsElementAddrs(internal.Method_optionsTag, 0, opts)
@@ -1425,6 +1431,7 @@ func (p *Printer) printMethod(mtd *desc.MethodDescriptor, mf *dynamic.MessageFac
 			fmt.Fprintln(w, "}")
 		} else {
 			fmt.Fprint(w, ";")
+			trailer(indent, false)
 		}
 	})
 }
@@ -2190,6 +2197,26 @@ func optionLess(i, j []option) bool {
 	return ni < nj
 }
 
+func (p *Printer) printBlockElement(isDecriptor bool, si *descriptor.SourceCodeInfo_Location, w *writer, indent int, el func(w *writer, trailer func(indent int, wantTrailingNewline bool))) {
+	includeComments := isDecriptor || p.includeCommentType(CommentsTokens)
+
+	if includeComments && si != nil {
+		p.printLeadingComments(si, w, indent)
+	}
+	el(w, func(indent int, wantTrailingNewline bool) {
+		if includeComments && si != nil {
+			if p.printTrailingComments(si, w, indent) && wantTrailingNewline && !p.Compact {
+				// separator line between trailing comment and next element
+				fmt.Fprintln(w)
+			}
+		}
+	})
+	if indent >= 0 && !w.newline {
+		// if we're not printing inline but element did not have trailing newline, add one now
+		fmt.Fprintln(w)
+	}
+}
+
 func (p *Printer) printElement(isDecriptor bool, si *descriptor.SourceCodeInfo_Location, w *writer, indent int, el func(*writer)) {
 	includeComments := isDecriptor || p.includeCommentType(CommentsTokens)
 
@@ -2260,7 +2287,7 @@ func (p *Printer) printLeadingComments(si *descriptor.SourceCodeInfo_Location, w
 	return endsInNewLine
 }
 
-func (p *Printer) printTrailingComments(si *descriptor.SourceCodeInfo_Location, w *writer, indent int) {
+func (p *Printer) printTrailingComments(si *descriptor.SourceCodeInfo_Location, w *writer, indent int) bool {
 	if p.includeCommentType(CommentsTrailing) && si.GetTrailingComments() != "" {
 		if !p.printComment(si.GetTrailingComments(), w, indent, p.TrailingCommentsOnSeparateLine) && indent >= 0 {
 			// trailing comment didn't end with newline but needs one
@@ -2269,7 +2296,10 @@ func (p *Printer) printTrailingComments(si *descriptor.SourceCodeInfo_Location,
 		} else if indent < 0 {
 			fmt.Fprint(w, " ")
 		}
+		return true
 	}
+
+	return false
 }
 
 func (p *Printer) printComment(comments string, w *writer, indent int, forceNextLine bool) bool {


Test patch:
diff --git a/desc/protoparse/test-source-info.txt b/desc/protoparse/test-source-info.txt
index 41c3926..3ee6550 100644
--- a/desc/protoparse/test-source-info.txt
+++ b/desc/protoparse/test-source-info.txt
@@ -3,7 +3,7 @@
 
 :
 desc_test_comments.proto:8:1
-desc_test_comments.proto:141:2
+desc_test_comments.proto:152:2
 
 
  > syntax:
@@ -60,7 +60,7 @@ desc_test_comments.proto:18:34
 
  > message_type[0]:
 desc_test_comments.proto:25:1
-desc_test_comments.proto:105:2
+desc_test_comments.proto:110:2
     Leading detached comment [0]:
  Multiple white space lines (like above) cannot
  be preserved...
@@ -68,9 +68,6 @@ desc_test_comments.proto:105:2
     Leading comments:
  We need a request for our RPC service below.
 
-    Trailing comments:
- And next we'll need some extensions...
-
 
 
  > message_type[0] > name:
@@ -357,7 +354,7 @@ desc_test_comments.proto:52:37
 
  > message_type[0] > field[2]:
 desc_test_comments.proto:55:9
-desc_test_comments.proto:67:10
+desc_test_comments.proto:69:10
 
 
  > message_type[0] > field[2] > label:
@@ -382,10 +379,13 @@ desc_test_comments.proto:55:51
 
  > message_type[0] > nested_type[0]:
 desc_test_comments.proto:55:9
-desc_test_comments.proto:67:10
+desc_test_comments.proto:69:10
     Leading comments:
  Group comment
 
+    Trailing comments:
+ trailer for Extras
+
 
 
  > message_type[0] > nested_type[0] > name:
@@ -401,81 +401,81 @@ desc_test_comments.proto:55:47
 
 
  > message_type[0] > nested_type[0] > options:
-desc_test_comments.proto:57:17
-desc_test_comments.proto:57:52
+desc_test_comments.proto:59:17
+desc_test_comments.proto:59:52
 
 
  > message_type[0] > nested_type[0] > options > mfubar:
-desc_test_comments.proto:57:17
-desc_test_comments.proto:57:52
+desc_test_comments.proto:59:17
+desc_test_comments.proto:59:52
     Leading comments:
  this is a custom option
 
 
 
  > message_type[0] > nested_type[0] > field[0]:
-desc_test_comments.proto:59:17
-desc_test_comments.proto:59:41
+desc_test_comments.proto:61:17
+desc_test_comments.proto:61:41
 
 
  > message_type[0] > nested_type[0] > field[0] > label:
-desc_test_comments.proto:59:17
-desc_test_comments.proto:59:25
+desc_test_comments.proto:61:17
+desc_test_comments.proto:61:25
 
 
  > message_type[0] > nested_type[0] > field[0] > type:
-desc_test_comments.proto:59:26
-desc_test_comments.proto:59:32
+desc_test_comments.proto:61:26
+desc_test_comments.proto:61:32
 
 
  > message_type[0] > nested_type[0] > field[0] > name:
-desc_test_comments.proto:59:33
-desc_test_comments.proto:59:36
+desc_test_comments.proto:61:33
+desc_test_comments.proto:61:36
 
 
  > message_type[0] > nested_type[0] > field[0] > number:
-desc_test_comments.proto:59:39
-desc_test_comments.proto:59:40
+desc_test_comments.proto:61:39
+desc_test_comments.proto:61:40
 
 
  > message_type[0] > nested_type[0] > field[1]:
-desc_test_comments.proto:60:17
-desc_test_comments.proto:60:40
+desc_test_comments.proto:62:17
+desc_test_comments.proto:62:40
 
 
  > message_type[0] > nested_type[0] > field[1] > label:
-desc_test_comments.proto:60:17
-desc_test_comments.proto:60:25
+desc_test_comments.proto:62:17
+desc_test_comments.proto:62:25
 
 
  > message_type[0] > nested_type[0] > field[1] > type:
-desc_test_comments.proto:60:26
-desc_test_comments.proto:60:31
+desc_test_comments.proto:62:26
+desc_test_comments.proto:62:31
 
 
  > message_type[0] > nested_type[0] > field[1] > name:
-desc_test_comments.proto:60:32
-desc_test_comments.proto:60:35
+desc_test_comments.proto:62:32
+desc_test_comments.proto:62:35
 
 
  > message_type[0] > nested_type[0] > field[1] > number:
-desc_test_comments.proto:60:38
-desc_test_comments.proto:60:39
+desc_test_comments.proto:62:38
+desc_test_comments.proto:62:39
 
 
  > message_type[0] > nested_type[0] > options:
-desc_test_comments.proto:62:17
-desc_test_comments.proto:62:64
+desc_test_comments.proto:64:17
+desc_test_comments.proto:64:64
 
 
  > message_type[0] > nested_type[0] > options > no_standard_descriptor_accessor:
-desc_test_comments.proto:62:17
-desc_test_comments.proto:62:64
+desc_test_comments.proto:64:17
+desc_test_comments.proto:64:64
 
 
  > message_type[0] > nested_type[0] > field[2]:
-desc_test_comments.proto:65:17
-desc_test_comments.proto:65:41
+desc_test_comments.proto:67:17
+desc_test_comments.proto:67:41
     Leading comments:
  Leading comment...
 
@@ -485,424 +485,433 @@ desc_test_comments.proto:65:41
 
 
  > message_type[0] > nested_type[0] > field[2] > label:
-desc_test_comments.proto:65:17
-desc_test_comments.proto:65:25
+desc_test_comments.proto:67:17
+desc_test_comments.proto:67:25
 
 
  > message_type[0] > nested_type[0] > field[2] > type:
-desc_test_comments.proto:65:26
-desc_test_comments.proto:65:32
+desc_test_comments.proto:67:26
+desc_test_comments.proto:67:32
 
 
  > message_type[0] > nested_type[0] > field[2] > name:
-desc_test_comments.proto:65:33
-desc_test_comments.proto:65:36
+desc_test_comments.proto:67:33
+desc_test_comments.proto:67:36
 
 
  > message_type[0] > nested_type[0] > field[2] > number:
-desc_test_comments.proto:65:39
-desc_test_comments.proto:65:40
+desc_test_comments.proto:67:39
+desc_test_comments.proto:67:40
 
 
  > message_type[0] > enum_type[0]:
-desc_test_comments.proto:69:9
-desc_test_comments.proto:90:10
+desc_test_comments.proto:71:9
+desc_test_comments.proto:93:10
+    Trailing comments:
+ trailer for enum
+
 
 
  > message_type[0] > enum_type[0] > name:
-desc_test_comments.proto:69:14
-desc_test_comments.proto:69:29
+desc_test_comments.proto:71:14
+desc_test_comments.proto:71:29
     Trailing comments:
  "super"!
 
 
 
  > message_type[0] > enum_type[0] > options:
-desc_test_comments.proto:72:17
-desc_test_comments.proto:72:43
+desc_test_comments.proto:75:17
+desc_test_comments.proto:75:43
 
 
  > message_type[0] > enum_type[0] > options > allow_alias:
-desc_test_comments.proto:72:17
-desc_test_comments.proto:72:43
+desc_test_comments.proto:75:17
+desc_test_comments.proto:75:43
     Leading comments:
  allow_alias comments!
 
 
 
  > message_type[0] > enum_type[0] > value[0]:
-desc_test_comments.proto:74:17
-desc_test_comments.proto:74:86
+desc_test_comments.proto:77:17
+desc_test_comments.proto:77:86
 
 
  > message_type[0] > enum_type[0] > value[0] > name:
-desc_test_comments.proto:74:17
-desc_test_comments.proto:74:22
+desc_test_comments.proto:77:17
+desc_test_comments.proto:77:22
 
 
  > message_type[0] > enum_type[0] > value[0] > number:
-desc_test_comments.proto:74:25
-desc_test_comments.proto:74:26
+desc_test_comments.proto:77:25
+desc_test_comments.proto:77:26
 
 
  > message_type[0] > enum_type[0] > value[0] > options:
-desc_test_comments.proto:74:27
-desc_test_comments.proto:74:85
+desc_test_comments.proto:77:27
+desc_test_comments.proto:77:85
 
 
  > message_type[0] > enum_type[0] > value[0] > options > evfubars:
-desc_test_comments.proto:74:28
-desc_test_comments.proto:74:56
+desc_test_comments.proto:77:28
+desc_test_comments.proto:77:56
 
 
  > message_type[0] > enum_type[0] > value[0] > options > evfubar:
-desc_test_comments.proto:74:58
-desc_test_comments.proto:74:84
+desc_test_comments.proto:77:58
+desc_test_comments.proto:77:84
 
 
  > message_type[0] > enum_type[0] > value[1]:
-desc_test_comments.proto:75:17
-desc_test_comments.proto:75:100
+desc_test_comments.proto:78:17
+desc_test_comments.proto:78:100
 
 
  > message_type[0] > enum_type[0] > value[1] > name:
-desc_test_comments.proto:75:17
-desc_test_comments.proto:75:22
+desc_test_comments.proto:78:17
+desc_test_comments.proto:78:22
 
 
  > message_type[0] > enum_type[0] > value[1] > number:
-desc_test_comments.proto:75:25
-desc_test_comments.proto:75:26
+desc_test_comments.proto:78:25
+desc_test_comments.proto:78:26
 
 
  > message_type[0] > enum_type[0] > value[1] > options:
-desc_test_comments.proto:75:27
-desc_test_comments.proto:75:99
+desc_test_comments.proto:78:27
+desc_test_comments.proto:78:99
 
 
  > message_type[0] > enum_type[0] > value[1] > options > evfubaruf:
-desc_test_comments.proto:75:29
-desc_test_comments.proto:75:57
+desc_test_comments.proto:78:29
+desc_test_comments.proto:78:57
 
 
  > message_type[0] > enum_type[0] > value[1] > options > evfubaru:
-desc_test_comments.proto:75:73
-desc_test_comments.proto:75:98
+desc_test_comments.proto:78:73
+desc_test_comments.proto:78:98
 
 
  > message_type[0] > enum_type[0] > value[2]:
-desc_test_comments.proto:76:17
-desc_test_comments.proto:76:27
+desc_test_comments.proto:79:17
+desc_test_comments.proto:79:27
 
 
  > message_type[0] > enum_type[0] > value[2] > name:
-desc_test_comments.proto:76:17
-desc_test_comments.proto:76:22
+desc_test_comments.proto:79:17
+desc_test_comments.proto:79:22
 
 
  > message_type[0] > enum_type[0] > value[2] > number:
-desc_test_comments.proto:76:25
-desc_test_comments.proto:76:26
+desc_test_comments.proto:79:25
+desc_test_comments.proto:79:26
 
 
  > message_type[0] > enum_type[0] > value[3]:
-desc_test_comments.proto:77:17
-desc_test_comments.proto:77:28
+desc_test_comments.proto:80:17
+desc_test_comments.proto:80:28
 
 
  > message_type[0] > enum_type[0] > value[3] > name:
-desc_test_comments.proto:77:17
-desc_test_comments.proto:77:23
+desc_test_comments.proto:80:17
+desc_test_comments.proto:80:23
 
 
  > message_type[0] > enum_type[0] > value[3] > number:
-desc_test_comments.proto:77:26
-desc_test_comments.proto:77:27
+desc_test_comments.proto:80:26
+desc_test_comments.proto:80:27
 
 
  > message_type[0] > enum_type[0] > options:
-desc_test_comments.proto:79:17
-desc_test_comments.proto:79:52
+desc_test_comments.proto:82:17
+desc_test_comments.proto:82:52
 
 
  > message_type[0] > enum_type[0] > options > efubars:
-desc_test_comments.proto:79:17
-desc_test_comments.proto:79:52
+desc_test_comments.proto:82:17
+desc_test_comments.proto:82:52
 
 
  > message_type[0] > enum_type[0] > value[4]:
-desc_test_comments.proto:81:17
-desc_test_comments.proto:81:27
+desc_test_comments.proto:84:17
+desc_test_comments.proto:84:27
 
 
  > message_type[0] > enum_type[0] > value[4] > name:
-desc_test_comments.proto:81:17
-desc_test_comments.proto:81:22
+desc_test_comments.proto:84:17
+desc_test_comments.proto:84:22
 
 
  > message_type[0] > enum_type[0] > value[4] > number:
-desc_test_comments.proto:81:25
-desc_test_comments.proto:81:26
+desc_test_comments.proto:84:25
+desc_test_comments.proto:84:26
 
 
  > message_type[0] > enum_type[0] > value[5]:
-desc_test_comments.proto:82:17
-desc_test_comments.proto:82:29
+desc_test_comments.proto:85:17
+desc_test_comments.proto:85:29
 
 
  > message_type[0] > enum_type[0] > value[5] > name:
-desc_test_comments.proto:82:17
-desc_test_comments.proto:82:24
+desc_test_comments.proto:85:17
+desc_test_comments.proto:85:24
 
 
  > message_type[0] > enum_type[0] > value[5] > number:
-desc_test_comments.proto:82:27
-desc_test_comments.proto:82:28
+desc_test_comments.proto:85:27
+desc_test_comments.proto:85:28
 
 
  > message_type[0] > enum_type[0] > value[6]:
-desc_test_comments.proto:83:17
-desc_test_comments.proto:83:60
+desc_test_comments.proto:86:17
+desc_test_comments.proto:86:60
 
 
  > message_type[0] > enum_type[0] > value[6] > name:
-desc_test_comments.proto:83:17
-desc_test_comments.proto:83:24
+desc_test_comments.proto:86:17
+desc_test_comments.proto:86:24
 
 
  > message_type[0] > enum_type[0] > value[6] > number:
-desc_test_comments.proto:83:27
-desc_test_comments.proto:83:28
+desc_test_comments.proto:86:27
+desc_test_comments.proto:86:28
 
 
  > message_type[0] > enum_type[0] > value[6] > options:
-desc_test_comments.proto:83:29
-desc_test_comments.proto:83:59
+desc_test_comments.proto:86:29
+desc_test_comments.proto:86:59
 
 
  > message_type[0] > enum_type[0] > value[6] > options > evfubarsf:
-desc_test_comments.proto:83:30
-desc_test_comments.proto:83:58
+desc_test_comments.proto:86:30
+desc_test_comments.proto:86:58
 
 
  > message_type[0] > enum_type[0] > value[7]:
-desc_test_comments.proto:84:17
-desc_test_comments.proto:84:28
+desc_test_comments.proto:87:17
+desc_test_comments.proto:87:28
 
 
  > message_type[0] > enum_type[0] > value[7] > name:
-desc_test_comments.proto:84:17
-desc_test_comments.proto:84:23
+desc_test_comments.proto:87:17
+desc_test_comments.proto:87:23
 
 
  > message_type[0] > enum_type[0] > value[7] > number:
-desc_test_comments.proto:84:26
-desc_test_comments.proto:84:27
+desc_test_comments.proto:87:26
+desc_test_comments.proto:87:27
 
 
  > message_type[0] > enum_type[0] > value[8]:
-desc_test_comments.proto:85:17
-desc_test_comments.proto:85:31
+desc_test_comments.proto:88:17
+desc_test_comments.proto:88:31
 
 
  > message_type[0] > enum_type[0] > value[8] > name:
-desc_test_comments.proto:85:17
-desc_test_comments.proto:85:26
+desc_test_comments.proto:88:17
+desc_test_comments.proto:88:26
 
 
  > message_type[0] > enum_type[0] > value[8] > number:
-desc_test_comments.proto:85:29
-desc_test_comments.proto:85:30
+desc_test_comments.proto:88:29
+desc_test_comments.proto:88:30
 
 
  > message_type[0] > enum_type[0] > value[9]:
-desc_test_comments.proto:86:17
-desc_test_comments.proto:86:27
+desc_test_comments.proto:89:17
+desc_test_comments.proto:89:27
 
 
  > message_type[0] > enum_type[0] > value[9] > name:
-desc_test_comments.proto:86:17
-desc_test_comments.proto:86:22
+desc_test_comments.proto:89:17
+desc_test_comments.proto:89:22
 
 
  > message_type[0] > enum_type[0] > value[9] > number:
-desc_test_comments.proto:86:25
-desc_test_comments.proto:86:26
+desc_test_comments.proto:89:25
+desc_test_comments.proto:89:26
 
 
  > message_type[0] > enum_type[0] > value[10]:
-desc_test_comments.proto:87:17
-desc_test_comments.proto:87:31
+desc_test_comments.proto:90:17
+desc_test_comments.proto:90:31
 
 
  > message_type[0] > enum_type[0] > value[10] > name:
-desc_test_comments.proto:87:17
-desc_test_comments.proto:87:23
+desc_test_comments.proto:90:17
+desc_test_comments.proto:90:23
 
 
  > message_type[0] > enum_type[0] > value[10] > number:
-desc_test_comments.proto:87:26
-desc_test_comments.proto:87:30
+desc_test_comments.proto:90:26
+desc_test_comments.proto:90:30
 
 
  > message_type[0] > enum_type[0] > options:
-desc_test_comments.proto:89:17
-desc_test_comments.proto:89:50
+desc_test_comments.proto:92:17
+desc_test_comments.proto:92:50
 
 
  > message_type[0] > enum_type[0] > options > efubar:
-desc_test_comments.proto:89:17
-desc_test_comments.proto:89:50
+desc_test_comments.proto:92:17
+desc_test_comments.proto:92:50
 
 
  > message_type[0] > oneof_decl[0]:
-desc_test_comments.proto:93:9
-desc_test_comments.proto:96:10
+desc_test_comments.proto:96:9
+desc_test_comments.proto:101:10
     Leading comments:
  can be this or that
 
+    Trailing comments:
+ trailer for oneof abc
+
 
 
  > message_type[0] > oneof_decl[0] > name:
-desc_test_comments.proto:93:15
-desc_test_comments.proto:93:18
+desc_test_comments.proto:96:15
+desc_test_comments.proto:96:18
 
 
  > message_type[0] > field[3]:
-desc_test_comments.proto:94:17
-desc_test_comments.proto:94:33
+desc_test_comments.proto:99:17
+desc_test_comments.proto:99:33
 
 
  > message_type[0] > field[3] > type:
-desc_test_comments.proto:94:17
-desc_test_comments.proto:94:23
+desc_test_comments.proto:99:17
+desc_test_comments.proto:99:23
 
 
  > message_type[0] > field[3] > name:
-desc_test_comments.proto:94:24
-desc_test_comments.proto:94:28
+desc_test_comments.proto:99:24
+desc_test_comments.proto:99:28
 
 
  > message_type[0] > field[3] > number:
-desc_test_comments.proto:94:31
-desc_test_comments.proto:94:32
+desc_test_comments.proto:99:31
+desc_test_comments.proto:99:32
 
 
  > message_type[0] > field[4]:
-desc_test_comments.proto:95:17
-desc_test_comments.proto:95:32
+desc_test_comments.proto:100:17
+desc_test_comments.proto:100:32
 
 
  > message_type[0] > field[4] > type:
-desc_test_comments.proto:95:17
-desc_test_comments.proto:95:22
+desc_test_comments.proto:100:17
+desc_test_comments.proto:100:22
 
 
  > message_type[0] > field[4] > name:
-desc_test_comments.proto:95:23
-desc_test_comments.proto:95:27
+desc_test_comments.proto:100:23
+desc_test_comments.proto:100:27
 
 
  > message_type[0] > field[4] > number:
-desc_test_comments.proto:95:30
-desc_test_comments.proto:95:31
+desc_test_comments.proto:100:30
+desc_test_comments.proto:100:31
 
 
  > message_type[0] > oneof_decl[1]:
-desc_test_comments.proto:98:9
-desc_test_comments.proto:101:10
+desc_test_comments.proto:103:9
+desc_test_comments.proto:106:10
     Leading comments:
  can be these or those
 
 
 
  > message_type[0] > oneof_decl[1] > name:
-desc_test_comments.proto:98:15
-desc_test_comments.proto:98:18
+desc_test_comments.proto:103:15
+desc_test_comments.proto:103:18
 
 
  > message_type[0] > field[5]:
-desc_test_comments.proto:99:17
-desc_test_comments.proto:99:34
+desc_test_comments.proto:104:17
+desc_test_comments.proto:104:34
 
 
  > message_type[0] > field[5] > type:
-desc_test_comments.proto:99:17
-desc_test_comments.proto:99:23
+desc_test_comments.proto:104:17
+desc_test_comments.proto:104:23
 
 
  > message_type[0] > field[5] > name:
-desc_test_comments.proto:99:24
-desc_test_comments.proto:99:29
+desc_test_comments.proto:104:24
+desc_test_comments.proto:104:29
 
 
  > message_type[0] > field[5] > number:
-desc_test_comments.proto:99:32
-desc_test_comments.proto:99:33
+desc_test_comments.proto:104:32
+desc_test_comments.proto:104:33
 
 
  > message_type[0] > field[6]:
-desc_test_comments.proto:100:17
-desc_test_comments.proto:100:33
+desc_test_comments.proto:105:17
+desc_test_comments.proto:105:33
 
 
  > message_type[0] > field[6] > type:
-desc_test_comments.proto:100:17
-desc_test_comments.proto:100:22
+desc_test_comments.proto:105:17
+desc_test_comments.proto:105:22
 
 
  > message_type[0] > field[6] > name:
-desc_test_comments.proto:100:23
-desc_test_comments.proto:100:28
+desc_test_comments.proto:105:23
+desc_test_comments.proto:105:28
 
 
  > message_type[0] > field[6] > number:
-desc_test_comments.proto:100:31
-desc_test_comments.proto:100:32
+desc_test_comments.proto:105:31
+desc_test_comments.proto:105:32
 
 
  > message_type[0] > field[7]:
-desc_test_comments.proto:104:9
-desc_test_comments.proto:104:40
+desc_test_comments.proto:109:9
+desc_test_comments.proto:109:40
     Leading comments:
  map field
 
 
 
  > message_type[0] > field[7] > type_name:
-desc_test_comments.proto:104:9
-desc_test_comments.proto:104:28
+desc_test_comments.proto:109:9
+desc_test_comments.proto:109:28
 
 
  > message_type[0] > field[7] > name:
-desc_test_comments.proto:104:29
-desc_test_comments.proto:104:35
+desc_test_comments.proto:109:29
+desc_test_comments.proto:109:35
 
 
  > message_type[0] > field[7] > number:
-desc_test_comments.proto:104:38
-desc_test_comments.proto:104:39
+desc_test_comments.proto:109:38
+desc_test_comments.proto:109:39
 
 
  > extension:
-desc_test_comments.proto:108:1
-desc_test_comments.proto:117:2
+desc_test_comments.proto:114:1
+desc_test_comments.proto:125:2
+    Leading detached comment [0]:
+ And next we'll need some extensions...
+
     Trailing comments:
- extend trailer...
+ trailer for extend block
 
 
 
  > extension[0]:
-desc_test_comments.proto:114:9
-desc_test_comments.proto:114:37
+desc_test_comments.proto:122:9
+desc_test_comments.proto:122:37
     Leading comments:
  comment for guid1
 
 
 
  > extension[0] > extendee:
-desc_test_comments.proto:110:1
-desc_test_comments.proto:110:8
+desc_test_comments.proto:116:1
+desc_test_comments.proto:116:8
     Leading comments:
  extendee comment
 
@@ -912,66 +921,68 @@ desc_test_comments.proto:110:8
 
 
  > extension[0] > label:
-desc_test_comments.proto:114:9
-desc_test_comments.proto:114:17
+desc_test_comments.proto:122:9
+desc_test_comments.proto:122:17
 
 
  > extension[0] > type:
-desc_test_comments.proto:114:18
-desc_test_comments.proto:114:24
+desc_test_comments.proto:122:18
+desc_test_comments.proto:122:24
 
 
  > extension[0] > name:
-desc_test_comments.proto:114:25
-desc_test_comments.proto:114:30
+desc_test_comments.proto:122:25
+desc_test_comments.proto:122:30
 
 
  > extension[0] > number:
-desc_test_comments.proto:114:33
-desc_test_comments.proto:114:36
+desc_test_comments.proto:122:33
+desc_test_comments.proto:122:36
 
 
  > extension[1]:
-desc_test_comments.proto:116:9
-desc_test_comments.proto:116:37
+desc_test_comments.proto:124:9
+desc_test_comments.proto:124:37
     Leading comments:
  ... and a comment for guid2
 
 
 
  > extension[1] > extendee:
-desc_test_comments.proto:110:1
-desc_test_comments.proto:110:8
+desc_test_comments.proto:116:1
+desc_test_comments.proto:116:8
 
 
  > extension[1] > label:
-desc_test_comments.proto:116:9
-desc_test_comments.proto:116:17
+desc_test_comments.proto:124:9
+desc_test_comments.proto:124:17
 
 
  > extension[1] > type:
-desc_test_comments.proto:116:18
-desc_test_comments.proto:116:24
+desc_test_comments.proto:124:18
+desc_test_comments.proto:124:24
 
 
  > extension[1] > name:
-desc_test_comments.proto:116:25
-desc_test_comments.proto:116:30
+desc_test_comments.proto:124:25
+desc_test_comments.proto:124:30
 
 
  > extension[1] > number:
-desc_test_comments.proto:116:33
-desc_test_comments.proto:116:36
+desc_test_comments.proto:124:33
+desc_test_comments.proto:124:36
 
 
  > message_type[1]:
-desc_test_comments.proto:120:1
-desc_test_comments.proto:120:81
+desc_test_comments.proto:128:1
+desc_test_comments.proto:128:115
+    Trailing comments:
+ trailer for AnEmptyMessage 
 
 
  > message_type[1] > name:
-desc_test_comments.proto:120:36
-desc_test_comments.proto:120:50
+desc_test_comments.proto:128:36
+desc_test_comments.proto:128:50
     Leading comments:
  name leading comment 
     Trailing comments:
@@ -979,83 +990,87 @@ desc_test_comments.proto:120:50
 
 
  > service[0]:
-desc_test_comments.proto:123:1
-desc_test_comments.proto:141:2
+desc_test_comments.proto:131:1
+desc_test_comments.proto:152:2
     Leading comments:
  Service comment
 
     Trailing comments:
  service trailer
+ that spans multiple lines
 
 
 
  > service[0] > name:
-desc_test_comments.proto:123:28
-desc_test_comments.proto:123:38
+desc_test_comments.proto:131:28
+desc_test_comments.proto:131:38
     Leading comments:
  service name 
 
 
  > service[0] > options:
-desc_test_comments.proto:125:9
-desc_test_comments.proto:125:43
+desc_test_comments.proto:136:9
+desc_test_comments.proto:136:43
 
 
  > service[0] > options > sfubar > id:
-desc_test_comments.proto:125:9
-desc_test_comments.proto:125:43
+desc_test_comments.proto:136:9
+desc_test_comments.proto:136:43
     Leading comments:
  option that sets field
 
 
 
  > service[0] > options:
-desc_test_comments.proto:127:9
-desc_test_comments.proto:127:47
+desc_test_comments.proto:138:9
+desc_test_comments.proto:138:47
 
 
  > service[0] > options > sfubar > name:
-desc_test_comments.proto:127:9
-desc_test_comments.proto:127:47
+desc_test_comments.proto:138:9
+desc_test_comments.proto:138:47
     Leading comments:
  another option that sets field
 
 
 
  > service[0] > options:
-desc_test_comments.proto:128:9
-desc_test_comments.proto:128:35
+desc_test_comments.proto:139:9
+desc_test_comments.proto:139:35
 
 
  > service[0] > options > deprecated:
-desc_test_comments.proto:128:9
-desc_test_comments.proto:128:35
+desc_test_comments.proto:139:9
+desc_test_comments.proto:139:35
     Trailing comments:
  DEPRECATED!
 
 
 
  > service[0] > options:
-desc_test_comments.proto:130:9
-desc_test_comments.proto:130:45
+desc_test_comments.proto:141:9
+desc_test_comments.proto:141:45
 
 
  > service[0] > options > sfubare:
-desc_test_comments.proto:130:9
-desc_test_comments.proto:130:45
+desc_test_comments.proto:141:9
+desc_test_comments.proto:141:45
 
 
  > service[0] > method[0]:
-desc_test_comments.proto:133:9
-desc_test_comments.proto:134:84
+desc_test_comments.proto:144:9
+desc_test_comments.proto:145:84
     Leading comments:
  Method comment
 
+    Trailing comments:
+ compact method trailer
+
 
 
  > service[0] > method[0] > name:
-desc_test_comments.proto:133:28
-desc_test_comments.proto:133:40
+desc_test_comments.proto:144:28
+desc_test_comments.proto:144:40
     Leading comments:
  rpc name 
     Trailing comments:
@@ -1063,74 +1078,77 @@ desc_test_comments.proto:133:40
 
 
  > service[0] > method[0] > client_streaming:
-desc_test_comments.proto:133:73
-desc_test_comments.proto:133:79
+desc_test_comments.proto:144:73
+desc_test_comments.proto:144:79
     Leading comments:
  comment B 
 
 
  > service[0] > method[0] > input_type:
-desc_test_comments.proto:133:96
-desc_test_comments.proto:133:103
+desc_test_comments.proto:144:96
+desc_test_comments.proto:144:103
     Leading comments:
  comment C 
 
 
  > service[0] > method[0] > output_type:
-desc_test_comments.proto:134:57
-desc_test_comments.proto:134:64
+desc_test_comments.proto:145:57
+desc_test_comments.proto:145:64
     Leading comments:
 comment E 
 
 
  > service[0] > method[1]:
-desc_test_comments.proto:136:9
-desc_test_comments.proto:140:10
+desc_test_comments.proto:147:9
+desc_test_comments.proto:151:10
+    Trailing comments:
+ trailer for method
+
 
 
  > service[0] > method[1] > name:
-desc_test_comments.proto:136:13
-desc_test_comments.proto:136:21
+desc_test_comments.proto:147:13
+desc_test_comments.proto:147:21
 
 
  > service[0] > method[1] > input_type:
-desc_test_comments.proto:136:23
-desc_test_comments.proto:136:30
+desc_test_comments.proto:147:23
+desc_test_comments.proto:147:30
 
 
  > service[0] > method[1] > output_type:
-desc_test_comments.proto:136:41
-desc_test_comments.proto:136:62
+desc_test_comments.proto:147:41
+desc_test_comments.proto:147:62
 
 
  > service[0] > method[1] > options:
-desc_test_comments.proto:137:17
-desc_test_comments.proto:137:42
+desc_test_comments.proto:148:17
+desc_test_comments.proto:148:42
 
 
  > service[0] > method[1] > options > deprecated:
-desc_test_comments.proto:137:17
-desc_test_comments.proto:137:42
+desc_test_comments.proto:148:17
+desc_test_comments.proto:148:42
 
 
  > service[0] > method[1] > options:
-desc_test_comments.proto:138:17
-desc_test_comments.proto:138:53
+desc_test_comments.proto:149:17
+desc_test_comments.proto:149:53
 
 
  > service[0] > method[1] > options > mtfubar[0]:
-desc_test_comments.proto:138:17
-desc_test_comments.proto:138:53
+desc_test_comments.proto:149:17
+desc_test_comments.proto:149:53
 
 
  > service[0] > method[1] > options:
-desc_test_comments.proto:139:17
-desc_test_comments.proto:139:56
+desc_test_comments.proto:150:17
+desc_test_comments.proto:150:56
 
 
  > service[0] > method[1] > options > mtfubard:
-desc_test_comments.proto:139:17
-desc_test_comments.proto:139:56
+desc_test_comments.proto:150:17
+desc_test_comments.proto:150:56
 ---- desc_test_complex.proto ----
 
 
@@ -5261,8 +5279,6 @@ desc_test_complex.proto:268:48
  > message_type[9]:
 desc_test_complex.proto:271:1
 desc_test_complex.proto:296:2
-    Trailing comments:
- comment for last element in file, KeywordCollisionOptions
 
 
  > message_type[9] > name:
diff --git a/desc/protoprint/testfiles/test-non-files-full.txt b/desc/protoprint/testfiles/test-non-files-full.txt
index 6e5de49..34e50ba 100644
--- a/desc/protoprint/testfiles/test-non-files-full.txt
+++ b/desc/protoprint/testfiles/test-non-files-full.txt
@@ -54,6 +54,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // Group comment
   optional group Extras = 3 {
+    // trailer for Extras
+
     // this is a custom option
     option (testprotos.mfubar) = false;
 
@@ -68,6 +70,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   }
 
   enum MarioCharacters /* "super"! */ {
+    // trailer for enum
+
     // allow_alias comments!
     option allow_alias = true;
 
@@ -100,6 +104,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // can be this or that
   oneof abc {
+    // trailer for oneof abc
+
     string this = 4;
 
     int32 that = 5;
@@ -115,22 +121,29 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   // map field
   map<string, string> things = 8;
 }
+
 // And next we'll need some extensions...
 
 extend /* extendee comment */ Request /* extendee trailer */ {
+  // trailer for extend block
+
   // comment for guid1
   optional uint64 guid1 = 123;
 
   // ... and a comment for guid2
   optional uint64 guid2 = 124;
 }
-// extend trailer...
 
 message /* name leading comment */ AnEmptyMessage /* name trailing comment */ {
+  // trailer for AnEmptyMessage
+
 }
 
 // Service comment
 service /* service name */ RpcService {
+  // service trailer
+  // that spans multiple lines
+
   // option that sets field
   option (testprotos.sfubar) = { id:100 name:"bob"  };
 
@@ -139,9 +152,11 @@ service /* service name */ RpcService {
   option (testprotos.sfubare) = VALUE;
 
   // Method comment
-  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request );
+  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request ); // compact method trailer
 
   rpc UnaryRpc ( Request ) returns ( google.protobuf.Empty ) {
+    // trailer for method
+
     option deprecated = true;
 
     option (testprotos.mtfubar) = 12.340000;
@@ -149,7 +164,6 @@ service /* service name */ RpcService {
     option (testprotos.mtfubard) = 123.456000;
   }
 }
-// service trailer
 -------- foo.bar.Request (*desc.MessageDescriptor) --------
 // Multiple white space lines (like above) cannot
 // be preserved...
@@ -187,6 +201,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // Group comment
   optional group Extras = 3 {
+    // trailer for Extras
+
     // this is a custom option
     option (testprotos.mfubar) = false;
 
@@ -201,6 +217,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   }
 
   enum MarioCharacters /* "super"! */ {
+    // trailer for enum
+
     // allow_alias comments!
     option allow_alias = true;
 
@@ -233,6 +251,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // can be this or that
   oneof abc {
+    // trailer for oneof abc
+
     string this = 4;
 
     int32 that = 5;
@@ -248,7 +268,6 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   // map field
   map<string, string> things = 8;
 }
-// And next we'll need some extensions...
 -------- foo.bar.Request.ids (*desc.FieldDescriptor) --------
 // A field comment
 repeated int32 ids = /* detached tag */ /* tag numero uno */ 1 /*
@@ -268,6 +287,8 @@ optional /* type comment */ string /* name comment */ name = 2 [/* default lead
 -------- foo.bar.Request.extras (*desc.FieldDescriptor) --------
 // Group comment
 optional group Extras = 3 {
+  // trailer for Extras
+
   // this is a custom option
   option (testprotos.mfubar) = false;
 
@@ -306,6 +327,8 @@ oneof xyz {
 -------- foo.bar.Request.Extras (*desc.MessageDescriptor) --------
 // Group comment
 message /* group name */ Extras {
+  // trailer for Extras
+
   // this is a custom option
   option (testprotos.mfubar) = false;
 
@@ -339,6 +362,8 @@ optional string key = 1;
 optional string value = 2;
 -------- foo.bar.Request.MarioCharacters (*desc.EnumDescriptor) --------
 enum MarioCharacters /* "super"! */ {
+  // trailer for enum
+
   // allow_alias comments!
   option allow_alias = true;
 
@@ -392,6 +417,8 @@ KAMEK = 8;
 SNIFIT = -101;
 -------- foo.bar.AnEmptyMessage (*desc.MessageDescriptor) --------
 message /* name leading comment */ AnEmptyMessage /* name trailing comment */ {
+  // trailer for AnEmptyMessage
+
 }
 -------- foo.bar.guid1 (*desc.FieldDescriptor) --------
 extend /* extendee comment */ Request /* extendee trailer */ {
@@ -406,6 +433,9 @@ extend Request {
 -------- foo.bar.RpcService (*desc.ServiceDescriptor) --------
 // Service comment
 service /* service name */ RpcService {
+  // service trailer
+  // that spans multiple lines
+
   // option that sets field
   option (testprotos.sfubar) = { id:100 name:"bob"  };
 
@@ -414,9 +444,11 @@ service /* service name */ RpcService {
   option (testprotos.sfubare) = VALUE;
 
   // Method comment
-  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request );
+  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request ); // compact method trailer
 
   rpc UnaryRpc ( Request ) returns ( google.protobuf.Empty ) {
+    // trailer for method
+
     option deprecated = true;
 
     option (testprotos.mtfubar) = 12.340000;
@@ -424,12 +456,13 @@ service /* service name */ RpcService {
     option (testprotos.mtfubard) = 123.456000;
   }
 }
-// service trailer
 -------- foo.bar.RpcService.StreamingRpc (*desc.MethodDescriptor) --------
 // Method comment
-rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request );
+rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request ); // compact method trailer
 -------- foo.bar.RpcService.UnaryRpc (*desc.MethodDescriptor) --------
 rpc UnaryRpc ( Request ) returns ( google.protobuf.Empty ) {
+  // trailer for method
+
   option deprecated = true;
 
   option (testprotos.mtfubar) = 12.340000;
diff --git a/desc/protoprint/testfiles/test-preserve-comments.proto b/desc/protoprint/testfiles/test-preserve-comments.proto
index 75223de..4c532c1 100644
--- a/desc/protoprint/testfiles/test-preserve-comments.proto
+++ b/desc/protoprint/testfiles/test-preserve-comments.proto
@@ -53,6 +53,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // Group comment
   optional group Extras = 3 {
+    // trailer for Extras
+
     // this is a custom option
     option (testprotos.mfubar) = false;
 
@@ -67,6 +69,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   }
 
   enum MarioCharacters /* "super"! */ {
+    // trailer for enum
+
     // allow_alias comments!
     option allow_alias = true;
 
@@ -99,6 +103,8 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
 
   // can be this or that
   oneof abc {
+    // trailer for oneof abc
+
     string this = 4;
 
     int32 that = 5;
@@ -114,22 +120,29 @@ message /* detached message name */ /* request with a capital R */ Request /* tr
   // map field
   map<string, string> things = 8;
 }
+
 // And next we'll need some extensions...
 
 extend /* extendee comment */ Request /* extendee trailer */ {
+  // trailer for extend block
+
   // comment for guid1
   optional uint64 guid1 = 123;
 
   // ... and a comment for guid2
   optional uint64 guid2 = 124;
 }
-// extend trailer...
 
 message /* name leading comment */ AnEmptyMessage /* name trailing comment */ {
+  // trailer for AnEmptyMessage
+
 }
 
 // Service comment
 service /* service name */ RpcService {
+  // service trailer
+  // that spans multiple lines
+
   // option that sets field
   option (testprotos.sfubar) = { id:100 name:"bob"  };
 
@@ -138,9 +151,11 @@ service /* service name */ RpcService {
   option (testprotos.sfubare) = VALUE;
 
   // Method comment
-  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request );
+  rpc /* rpc name */ StreamingRpc /* comment A */ ( /* comment C */ stream Request ) returns ( /*comment E */ Request ); // compact method trailer
 
   rpc UnaryRpc ( Request ) returns ( google.protobuf.Empty ) {
+    // trailer for method
+
     option deprecated = true;
 
     option (testprotos.mtfubar) = 12.340000;
@@ -148,4 +163,3 @@ service /* service name */ RpcService {
     option (testprotos.mtfubard) = 123.456000;
   }
 }
-// service trailer
diff --git a/internal/testprotos/desc_test_comments.proto b/internal/testprotos/desc_test_comments.proto
index c72f35a..9d6151b 100644
--- a/internal/testprotos/desc_test_comments.proto
+++ b/internal/testprotos/desc_test_comments.proto
@@ -53,6 +53,8 @@ message /* detached message name */ /* request with a capital R */ Request // tr
 
 	// Group comment
 	optional group /* group name */ Extras = 3 {
+		// trailer for Extras
+
 		// this is a custom option
 		option (testprotos.mfubar) = false;
 
@@ -67,7 +69,8 @@ message /* detached message name */ /* request with a capital R */ Request // tr
 	}
 
 	enum MarioCharacters // "super"!
-	{
+	{ // trailer for enum
+
 		// allow_alias comments!
 		option allow_alias = true;
 
@@ -91,6 +94,8 @@ message /* detached message name */ /* request with a capital R */ Request // tr
 
 	// can be this or that
 	oneof abc {
+		// trailer for oneof abc
+
 		string this = 4;
 		int32 that = 5;
 	}
@@ -103,6 +108,7 @@ message /* detached message name */ /* request with a capital R */ Request // tr
 	// map field
 	map<string, string> things = 8;
 }
+
 // And next we'll need some extensions...
 
 extend
@@ -110,17 +116,22 @@ extend
 Request
 // extendee trailer
 {
+	// trailer for extend block
+
 	// comment for guid1
 	optional uint64 guid1 = 123;
 	// ... and a comment for guid2
 	optional uint64 guid2 = 124;
 }
-// extend trailer...
+// after extend block
 
-message /* name leading comment */ AnEmptyMessage /* name trailing comment */ {}
+message /* name leading comment */ AnEmptyMessage /* name trailing comment */ { /* trailer for AnEmptyMessage */ }
 
 // Service comment
 service /* service name */ RpcService {
+	// service trailer
+	// that spans multiple lines
+
 	// option that sets field
 	option(testprotos.sfubar).id= 100;
 	// another option that sets field
@@ -131,14 +142,14 @@ service /* service name */ RpcService {
 
 	// Method comment
 	rpc /* rpc name */ StreamingRpc /* comment A */ (/* comment B */stream /* comment C */ Request)
-		returns /* comment D */ (/*comment E */ Request ) /* comment F */ ;
+		returns /* comment D */ (/*comment E */ Request ) /* comment F */ ; // compact method trailer
 
-	rpc UnaryRpc (Request) returns (google.protobuf.Empty) {
+	rpc UnaryRpc (Request) returns (google.protobuf.Empty) { // trailer for method
 		option deprecated = true;
 		option (testprotos.mtfubar) = 12.34;
 		option (testprotos.mtfubard) = 123.456;
 	}
 }
-// service trailer
+// another comment after service
 
 // Detached comment after all elements cannot be preserved...