From 814c2688a3e7003d5a77133ba16c523f8bf0a12b Mon Sep 17 00:00:00 2001 From: efirestone Date: Thu, 23 Apr 2026 11:17:18 -0700 Subject: [PATCH] Preserve leading/trailing comment split and fix blank doc-line rendering Adds round-trip fidelity for two ProtoParser gaps that share the same code path: 1. Leading vs. trailing comment split. FieldElement, EnumConstantElement, and ReservedElement gain a trailingDocumentation: String = "" field. The parser populates it; toSchema() emits same-line trailing comments inline (// ... for single-line, /* ... */ for multi-line block trailing) instead of merging them into the leading block above the declaration. 2. Blank line inside a doc paragraph. Util.appendDocumentation now emits "//\n" instead of "// \n", eliminating the trailing-whitespace artifact. The API is additive and non-breaking: the existing documentation field keeps its current merged semantics ("$leading\n$trailing" when both are present), so consumers that only read documentation see no change. Parser-constructed elements always populate the new field; elements constructed by other means default to "". SyntaxReader.tryAppendTrailingDocumentation now returns a small DocumentationWithTrailing(merged, trailing) value. Shared helpers leadingDocumentation(...) and appendTrailingDocumentation(...) in Util.kt keep the printer logic uniform across the three elements. Co-Authored-By: Claude Opus 4.7 --- .../com/squareup/wire/schema/EnumConstant.kt | 8 +- .../com/squareup/wire/schema/Reserved.kt | 8 +- .../com/squareup/wire/schema/internal/Util.kt | 42 ++++++- .../internal/parser/EnumConstantElement.kt | 9 +- .../schema/internal/parser/FieldElement.kt | 9 +- .../schema/internal/parser/ProtoParser.kt | 18 +-- .../schema/internal/parser/ReservedElement.kt | 9 +- .../schema/internal/parser/SyntaxReader.kt | 17 ++- .../schema/internal/parser/EnumElementTest.kt | 45 +++++++ .../internal/parser/FieldElementTest.kt | 95 ++++++++++++++ .../internal/parser/MessageElementTest.kt | 44 +++++++ .../schema/internal/parser/ProtoParserTest.kt | 117 ++++++++++++++++++ 12 files changed, 396 insertions(+), 25 deletions(-) diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/EnumConstant.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/EnumConstant.kt index 31e5d8bc6e..b92adc66cb 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/EnumConstant.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/EnumConstant.kt @@ -28,7 +28,13 @@ data class EnumConstant( val isDeprecated: Boolean get() = "true" == options.get(DEPRECATED) - internal fun toElement() = EnumConstantElement(location, name, tag, documentation, options.elements) + internal fun toElement() = EnumConstantElement( + location = location, + name = name, + tag = tag, + documentation = documentation, + options = options.elements, + ) internal fun linkOptions(linker: Linker, validate: Boolean) { @Suppress("NAME_SHADOWING") diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Reserved.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Reserved.kt index fbac2277e5..25b69a4dae 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Reserved.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Reserved.kt @@ -34,6 +34,12 @@ data class Reserved( fun fromElements(elements: List) = elements.map { Reserved(it.location, it.documentation, it.values) } @JvmStatic - fun toElements(reserveds: List) = reserveds.map { ReservedElement(it.location, it.documentation, it.values) } + fun toElements(reserveds: List) = reserveds.map { + ReservedElement( + location = it.location, + documentation = it.documentation, + values = it.values, + ) + } } } diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/Util.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/Util.kt index ec0fe9f4f6..c5b1eed683 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/Util.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/Util.kt @@ -39,9 +39,45 @@ fun StringBuilder.appendDocumentation( lines = lines.dropLast(1) } for (line in lines) { - append("// ") - .append(line) - .append('\n') + if (line.isEmpty()) { + append("//\n") + } else { + append("// ").append(line).append('\n') + } + } +} + +/** + * Extracts the leading-only portion of a declaration's documentation, given + * the merged form produced by the parser (which stores leading and trailing + * joined by `\n`) and the trailing piece reported separately. + */ +// TODO internal and friend for wire-java-generator: https://youtrack.jetbrains.com/issue/KT-34102 +fun leadingDocumentation( + documentation: String, + trailingDocumentation: String, +): String { + if (trailingDocumentation.isEmpty()) return documentation + if (documentation == trailingDocumentation) return "" + return documentation.removeSuffix("\n$trailingDocumentation") +} + +/** + * Appends a same-line trailing comment. Emits `// text` for single-line content + * and `/* text */` for multi-line content. The caller must ensure + * [trailingDocumentation] does not contain `*` followed by `/`; parser-produced + * values are always safe, but manually-constructed elements must not violate + * this precondition. + */ +fun StringBuilder.appendTrailingDocumentation( + trailingDocumentation: String, +) { + if (trailingDocumentation.isEmpty()) return + append(' ') + if (trailingDocumentation.contains('\n')) { + append("/* ").append(trailingDocumentation).append(" */") + } else { + append("// ").append(trailingDocumentation) } } diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/EnumConstantElement.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/EnumConstantElement.kt index ab868a9b8c..b6ad28a8b8 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/EnumConstantElement.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/EnumConstantElement.kt @@ -18,23 +18,28 @@ package com.squareup.wire.schema.internal.parser import com.squareup.wire.schema.Location import com.squareup.wire.schema.internal.appendDocumentation import com.squareup.wire.schema.internal.appendOptions +import com.squareup.wire.schema.internal.appendTrailingDocumentation +import com.squareup.wire.schema.internal.leadingDocumentation data class EnumConstantElement( val location: Location, val name: String, val tag: Int, val documentation: String = "", + val trailingDocumentation: String = "", val options: List = emptyList(), ) { fun toSchema() = buildString { - appendDocumentation(documentation) + appendDocumentation(leadingDocumentation(documentation, trailingDocumentation)) append("$name = $tag") if (options.isNotEmpty()) { append(" ") appendOptions(options) } - append(";\n") + append(';') + appendTrailingDocumentation(trailingDocumentation) + append('\n') } } diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/FieldElement.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/FieldElement.kt index 9a06c468e1..386d95dc2e 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/FieldElement.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/FieldElement.kt @@ -20,6 +20,8 @@ import com.squareup.wire.schema.Location import com.squareup.wire.schema.ProtoType import com.squareup.wire.schema.internal.appendDocumentation import com.squareup.wire.schema.internal.appendOptions +import com.squareup.wire.schema.internal.appendTrailingDocumentation +import com.squareup.wire.schema.internal.leadingDocumentation import com.squareup.wire.schema.internal.toEnglishLowerCase data class FieldElement( @@ -31,10 +33,11 @@ data class FieldElement( val jsonName: String? = null, val tag: Int = 0, val documentation: String = "", + val trailingDocumentation: String = "", val options: List = emptyList(), ) { fun toSchema() = buildString { - appendDocumentation(documentation) + appendDocumentation(leadingDocumentation(documentation, trailingDocumentation)) if (label != null) { append("${label.name.toEnglishLowerCase()} ") @@ -47,7 +50,9 @@ data class FieldElement( appendOptions(optionsWithDefault) } - append(";\n") + append(';') + appendTrailingDocumentation(trailingDocumentation) + append('\n') } /** diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ProtoParser.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ProtoParser.kt index b4495d91f5..98aa53f26a 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ProtoParser.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ProtoParser.kt @@ -377,8 +377,7 @@ class ProtoParser internal constructor( val jsonName = stripJsonName(options) reader.require(';') - @Suppress("NAME_SHADOWING") - val documentation = reader.tryAppendTrailingDocumentation(documentation) + val withTrailing = reader.tryAppendTrailingDocumentation(documentation) return FieldElement( location = location, @@ -388,7 +387,8 @@ class ProtoParser internal constructor( defaultValue = defaultValue, jsonName = jsonName, tag = tag, - documentation = documentation, + documentation = withTrailing.merged, + trailingDocumentation = withTrailing.trailing, options = options.toList(), ) } @@ -517,12 +517,12 @@ class ProtoParser internal constructor( "'reserved' must have at least one field name or tag" } - @Suppress("NAME_SHADOWING") - val documentation = reader.tryAppendTrailingDocumentation(documentation) + val withTrailing = reader.tryAppendTrailingDocumentation(documentation) return ReservedElement( location = location, - documentation = documentation, + documentation = withTrailing.merged, + trailingDocumentation = withTrailing.trailing, values = values, ) } @@ -580,14 +580,14 @@ class ProtoParser internal constructor( val options = OptionReader(reader).readOptions() reader.require(';') - @Suppress("NAME_SHADOWING") - val documentation = reader.tryAppendTrailingDocumentation(documentation) + val withTrailing = reader.tryAppendTrailingDocumentation(documentation) return EnumConstantElement( location = location, name = label, tag = tag, - documentation = documentation, + documentation = withTrailing.merged, + trailingDocumentation = withTrailing.trailing, options = options, ) } diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ReservedElement.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ReservedElement.kt index 9d1f8a3c2b..0cd28f5a2e 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ReservedElement.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/ReservedElement.kt @@ -18,15 +18,18 @@ package com.squareup.wire.schema.internal.parser import com.squareup.wire.schema.Location import com.squareup.wire.schema.internal.MAX_TAG_VALUE import com.squareup.wire.schema.internal.appendDocumentation +import com.squareup.wire.schema.internal.appendTrailingDocumentation +import com.squareup.wire.schema.internal.leadingDocumentation data class ReservedElement( val location: Location, val documentation: String = "", + val trailingDocumentation: String = "", /** A [String] name or [Int] or [IntRange] tag. */ val values: List, ) { fun toSchema() = buildString { - appendDocumentation(documentation) + appendDocumentation(leadingDocumentation(documentation, trailingDocumentation)) append("reserved ") values.forEachIndexed { index, value -> @@ -46,6 +49,8 @@ data class ReservedElement( else -> throw AssertionError() } } - append(";\n") + append(';') + appendTrailingDocumentation(trailingDocumentation) + append('\n') } } diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/SyntaxReader.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/SyntaxReader.kt index db6da39849..850e1cb4ff 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/SyntaxReader.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/SyntaxReader.kt @@ -306,7 +306,7 @@ class SyntaxReader( } } - fun tryAppendTrailingDocumentation(documentation: String): String { + internal fun tryAppendTrailingDocumentation(documentation: String): DocumentationWithTrailing { // Search for a '/' character ignoring spaces and tabs. loop@ while (pos < data.size) { when (data[pos]) { @@ -318,7 +318,7 @@ class SyntaxReader( } // Not a whitespace or comment-starting character. Return original documentation. - else -> return documentation + else -> return DocumentationWithTrailing(documentation, "") } } @@ -381,11 +381,11 @@ class SyntaxReader( end-- } - if (end == start) return documentation + if (end == start) return DocumentationWithTrailing(documentation, "") val trailingDocumentation = data.concatToString(start, end + 1) - if (documentation.isEmpty()) return trailingDocumentation - return "$documentation\n$trailingDocumentation" + if (documentation.isEmpty()) return DocumentationWithTrailing(trailingDocumentation, trailingDocumentation) + return DocumentationWithTrailing("$documentation\n$trailingDocumentation", trailingDocumentation) } /** @@ -425,3 +425,10 @@ class SyntaxReader( location: Location? = location(), ): RuntimeException = throw IllegalStateException("Syntax error in $location: $message") } + +internal data class DocumentationWithTrailing( + /** The merged documentation (leading + trailing), matching the pre-refactor return value. */ + val merged: String, + /** The trailing (same-line) comment only, or "" if no trailing comment was found. */ + val trailing: String, +) diff --git a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/EnumElementTest.kt b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/EnumElementTest.kt index 9f320f1fb2..bc83e50fd1 100644 --- a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/EnumElementTest.kt +++ b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/EnumElementTest.kt @@ -208,4 +208,49 @@ class EnumElementTest { """.trimMargin() assertThat(value.toSchema()).isEqualTo(expected) } + + @Test + fun enumConstantWithTrailingOnlyDocumentation() { + val constant = EnumConstantElement( + location = location, + name = "FOO", + tag = 1, + documentation = "inline doc", + trailingDocumentation = "inline doc", + ) + + assertThat(constant.toSchema()).isEqualTo("FOO = 1; // inline doc\n") + } + + @Test + fun enumConstantWithLeadingAndTrailingDocumentation() { + val constant = EnumConstantElement( + location = location, + name = "FOO", + tag = 1, + documentation = "above\ninline", + trailingDocumentation = "inline", + ) + + assertThat(constant.toSchema()).isEqualTo( + """ + |// above + |FOO = 1; // inline + | + """.trimMargin(), + ) + } + + @Test + fun enumConstantWithMultilineTrailingUsesBlockComment() { + val constant = EnumConstantElement( + location = location, + name = "FOO", + tag = 1, + documentation = "line one\nline two", + trailingDocumentation = "line one\nline two", + ) + + assertThat(constant.toSchema()).isEqualTo("FOO = 1; /* line one\nline two */\n") + } } diff --git a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/FieldElementTest.kt b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/FieldElementTest.kt index 439b8d7283..8739417bdd 100644 --- a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/FieldElementTest.kt +++ b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/FieldElementTest.kt @@ -128,4 +128,99 @@ class FieldElementTest { """.trimMargin(), ) } + + @Test + fun emptyDocLineHasNoTrailingSpace() { + val field = FieldElement( + location = location, + label = OPTIONAL, + type = "string", + name = "name", + tag = 1, + documentation = "First paragraph.\n\nSecond paragraph.", + ) + + assertThat(field.toSchema()).isEqualTo( + """ + |// First paragraph. + |// + |// Second paragraph. + |optional string name = 1; + | + """.trimMargin(), + ) + } + + @Test + fun fieldWithTrailingOnlyDocumentation() { + val field = FieldElement( + location = location, + label = OPTIONAL, + type = "string", + name = "name", + tag = 1, + documentation = "inline doc", + trailingDocumentation = "inline doc", + ) + + assertThat(field.toSchema()).isEqualTo("optional string name = 1; // inline doc\n") + } + + @Test + fun fieldWithLeadingAndTrailingDocumentation() { + val field = FieldElement( + location = location, + label = OPTIONAL, + type = "string", + name = "name", + tag = 1, + documentation = "above\ninline", + trailingDocumentation = "inline", + ) + + assertThat(field.toSchema()).isEqualTo( + """ + |// above + |optional string name = 1; // inline + | + """.trimMargin(), + ) + } + + @Test + fun fieldWithMultilineTrailingDocumentationUsesBlockComment() { + val field = FieldElement( + location = location, + label = OPTIONAL, + type = "string", + name = "name", + tag = 1, + documentation = "line one\nline two", + trailingDocumentation = "line one\nline two", + ) + + assertThat(field.toSchema()).isEqualTo( + "optional string name = 1; /* line one\nline two */\n", + ) + } + + @Test + fun fieldWithLeadingOnlyDocumentationUnchanged() { + val field = FieldElement( + location = location, + label = OPTIONAL, + type = "string", + name = "name", + tag = 1, + documentation = "above only", + ) + + assertThat(field.toSchema()).isEqualTo( + """ + |// above only + |optional string name = 1; + | + """.trimMargin(), + ) + } } diff --git a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/MessageElementTest.kt b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/MessageElementTest.kt index 3206c9c139..8f2df0ce8b 100644 --- a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/MessageElementTest.kt +++ b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/MessageElementTest.kt @@ -726,4 +726,48 @@ class MessageElementTest { ) assertThat(oneOf.toSchema()).isEqualTo(expected) } + + @Test + fun reservedWithTrailingOnlyDocumentation() { + val reserved = ReservedElement( + location = location, + documentation = "inline doc", + trailingDocumentation = "inline doc", + values = listOf(10), + ) + + assertThat(reserved.toSchema()).isEqualTo("reserved 10; // inline doc\n") + } + + @Test + fun reservedWithLeadingAndTrailingDocumentation() { + val reserved = ReservedElement( + location = location, + documentation = "above\ninline", + trailingDocumentation = "inline", + values = listOf(10), + ) + + assertThat(reserved.toSchema()).isEqualTo( + """ + |// above + |reserved 10; // inline + | + """.trimMargin(), + ) + } + + @Test + fun reservedWithMultilineTrailingUsesBlockComment() { + val reserved = ReservedElement( + location = location, + documentation = "line one\nline two", + trailingDocumentation = "line one\nline two", + values = listOf(10), + ) + + assertThat(reserved.toSchema()).isEqualTo( + "reserved 10; /* line one\nline two */\n", + ) + } } diff --git a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/ProtoParserTest.kt b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/ProtoParserTest.kt index f40835bb39..ba03241d37 100644 --- a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/ProtoParserTest.kt +++ b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/internal/parser/ProtoParserTest.kt @@ -468,6 +468,73 @@ class ProtoParserTest { assertThat(field.documentation).isEqualTo("Test all...\n...the things!") } + @Test + fun messageFieldLeadingAndTrailingCommentAreSeparable() { + val proto = """ + |message Test { + | // Leading. + | optional string name = 1; // Trailing. + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val field = (parsed.types[0] as MessageElement).fields[0] + assertThat(field.documentation).isEqualTo("Leading.\nTrailing.") + assertThat(field.trailingDocumentation).isEqualTo("Trailing.") + } + + @Test + fun messageFieldTrailingOnlyDocumentation() { + val proto = """ + |message Test { + | optional string name = 1; // Trailing only. + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val field = (parsed.types[0] as MessageElement).fields[0] + assertThat(field.documentation).isEqualTo("Trailing only.") + assertThat(field.trailingDocumentation).isEqualTo("Trailing only.") + } + + @Test + fun messageFieldLeadingOnlyDocumentation() { + val proto = """ + |message Test { + | // Leading only. + | optional string name = 1; + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val field = (parsed.types[0] as MessageElement).fields[0] + assertThat(field.documentation).isEqualTo("Leading only.") + assertThat(field.trailingDocumentation).isEqualTo("") + } + + @Test + fun messageFieldMultilineBlockTrailingPreservedInMessage() { + val proto = """ + |message Test { + | optional string name = 1; /* line one + |line two */ + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val field = (parsed.types[0] as MessageElement).fields[0] + assertThat(field.trailingDocumentation).isEqualTo("line one\nline two") + + // Round-trip through toSchema: the message body should indent each line of the + // block comment so the continuation line is properly indented. + val reemitted = (parsed.types[0] as MessageElement).toSchema() + assertThat(reemitted).isEqualTo( + """ + |message Test { + | optional string name = 1; /* line one + | line two */ + |} + | + """.trimMargin(), + ) + } + @Test fun trailingCommentNotAssignedToFollowingField() { val proto = """ @@ -527,6 +594,34 @@ class ProtoParserTest { assertThat(value.documentation).isEqualTo("Test all the\nthings!") } + @Test + fun enumValueLeadingAndTrailingCommentAreSeparable() { + val proto = """ + |enum Test { + | // Leading. + | FOO = 1; // Trailing. + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val constant = (parsed.types[0] as EnumElement).constants[0] + assertThat(constant.documentation).isEqualTo("Leading.\nTrailing.") + assertThat(constant.trailingDocumentation).isEqualTo("Trailing.") + } + + @Test + fun enumValueMultilineBlockTrailingPreserved() { + val proto = """ + |enum Test { + | FOO = 1; /* Test all the + |things! */ + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val constant = (parsed.types[0] as EnumElement).constants[0] + assertThat(constant.documentation).isEqualTo("Test all the\nthings!") + assertThat(constant.trailingDocumentation).isEqualTo("Test all the\nthings!") + } + @Test fun trailingMultilineCommentMustBeLastOnLineThrows() { val proto = """ @@ -2453,6 +2548,7 @@ class ProtoParserTest { name = "a", tag = 1, documentation = "This is A.", + trailingDocumentation = "This is A.", ), FieldElement( location = location.at(4, 3), @@ -2461,6 +2557,7 @@ class ProtoParserTest { name = "c", tag = 3, documentation = "This is C.", + trailingDocumentation = "This is C.", ), ), reserveds = listOf( @@ -2468,6 +2565,7 @@ class ProtoParserTest { location = location.at(3, 3), values = listOf(2), documentation = "This is reserved.", + trailingDocumentation = "This is reserved.", ), ), ) @@ -2478,6 +2576,19 @@ class ProtoParserTest { assertThat(ProtoParser.parse(location, proto)).isEqualTo(expected) } + @Test + fun reservedTrailingCommentSeparable() { + val proto = """ + |message Test { + | reserved 10; // trailing + |} + """.trimMargin() + val parsed = ProtoParser.parse(location, proto) + val reserved = (parsed.types[0] as MessageElement).reserveds[0] + assertThat(reserved.documentation).isEqualTo("trailing") + assertThat(reserved.trailingDocumentation).isEqualTo("trailing") + } + @Test fun noWhitespace() { val proto = "message C {optional A.B ab = 1;}" @@ -3227,6 +3338,7 @@ class ProtoParserTest { name = "IMAGE_STATE_UNSPECIFIED", tag = 0, documentation = "", + trailingDocumentation = "", options = listOf(), ), EnumConstantElement( @@ -3234,6 +3346,7 @@ class ProtoParserTest { name = "IMAGE_STATE_READONLY", tag = 1, documentation = "unlocked", + trailingDocumentation = "unlocked", options = listOf(), ), EnumConstantElement( @@ -3241,6 +3354,7 @@ class ProtoParserTest { name = "IMAGE_STATE_MUSTLOCK", tag = 2, documentation = "must be locked", + trailingDocumentation = "must be locked", options = listOf(), ), ), @@ -3272,6 +3386,7 @@ class ProtoParserTest { name = "IMAGE_STATE_UNSPECIFIED", tag = 0, documentation = "", + trailingDocumentation = "", options = listOf(), ), EnumConstantElement( @@ -3279,6 +3394,7 @@ class ProtoParserTest { name = "IMAGE_STATE_READONLY", tag = 1, documentation = "unlocked", + trailingDocumentation = "unlocked", options = listOf(), ), EnumConstantElement( @@ -3286,6 +3402,7 @@ class ProtoParserTest { name = "IMAGE_STATE_MUSTLOCK", tag = 2, documentation = "must be locked", + trailingDocumentation = "must be locked", options = listOf(), ), ),