Repository: jhump/protoreflect
Pull request #472 (2021-12-15 06:00:48)

Report errs via linker rather than parseResult
Prevents a panic when dealing with unused well-known types

pcj: Gotta love a single character PR!

Patch:
diff --git a/desc/protoparse/linker.go b/desc/protoparse/linker.go
index 4a1a4a5..e78750f 100644
--- a/desc/protoparse/linker.go
+++ b/desc/protoparse/linker.go
@@ -930,7 +930,7 @@ func (l *linker) checkForUnusedImports(filename string) {
 			if pos == nil {
 				pos = ast.UnknownPos(r.fd.GetName())
 			}
-			r.errs.warn(pos, errUnusedImport(dep))
+			l.errs.warn(pos, errUnusedImport(dep))
 		}
 	}
 }
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...