diff --git a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/CodeGenerator.kt b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/CodeGenerator.kt index 69645a24..da9e638b 100644 --- a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/CodeGenerator.kt +++ b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/CodeGenerator.kt @@ -78,6 +78,7 @@ public open class CodeGenerator( line() // Only mark top-level classes for export, internal classes will be exported transitively if (!nested) line("@pbandk.Export") + if (type.deprecated) line("@Deprecated(message = \"Enum marked deprecated in ${file.name}\")") // Enums are sealed classes w/ a value and a name, and a companion object with all values line("$visibility sealed class ${type.kotlinTypeName}(override val value: Int, override val name: String? = null) : pbandk.Message.Enum {") .indented { @@ -85,10 +86,14 @@ public open class CodeGenerator( line("override fun hashCode(): Int = value.hashCode()") line("override fun toString(): String = \"${type.kotlinFullTypeName}.\${name ?: \"UNRECOGNIZED\"}(value=\$value)\"") line() - type.values.forEach { line("$visibility object ${it.kotlinValueTypeName} : ${type.kotlinTypeName}(${it.number}, \"${it.name}\")") } + type.values.forEach { + if (it.deprecated) line("@Deprecated(message = \"Enum value marked deprecated in ${file.name}\")") + line("$visibility object ${it.kotlinValueTypeName} : ${type.kotlinTypeName}(${it.number}, \"${it.name}\")") + } line("$visibility class UNRECOGNIZED(value: Int) : ${type.kotlinTypeName}(value)") line() line("$visibility companion object : pbandk.Message.Enum.Companion<${file.kotlinPackageName}.${type.kotlinFullTypeName}> {").indented { + if (type.deprecated || type.values.any { it.deprecated }) line("@Suppress(\"DEPRECATION\")") line("$visibility val values: List<${file.kotlinPackageName}.${type.kotlinFullTypeName}> by lazy { listOf(${type.values.joinToString(", ") { it.kotlinValueTypeName }}) }") line("override fun fromValue(value: Int): ${file.kotlinPackageName}.${type.kotlinFullTypeName} = values.firstOrNull { it.value == value } ?: UNRECOGNIZED(value)") line("override fun fromName(name: String): ${file.kotlinPackageName}.${type.kotlinFullTypeName} = values.firstOrNull { it.name == name } ?: throw IllegalArgumentException(\"No ${type.kotlinTypeName} with name: \$name\")") @@ -104,6 +109,7 @@ public open class CodeGenerator( line() // Only mark top-level classes for export, internal classes will be exported transitively if (!nested) line("@pbandk.Export") + if (type.deprecated) line("@Deprecated(message = \"Message marked deprecated in ${file.name}\")") line("$visibility data class ${type.kotlinTypeName}(").indented { val fieldBegin = if (type.mapEntry) "override " else "" type.fields.forEach { field -> @@ -298,6 +304,7 @@ public open class CodeGenerator( // have to get rid of (i.e. `Person.AddressBook` becomes `PersonAddressBook`). line("@pbandk.Export") line("@pbandk.JsName(\"orDefaultFor${type.kotlinFullTypeName.replace(".", "")}\")") + if (type.deprecated) line("@Suppress(\"DEPRECATION\")") line("$visibility fun ${type.kotlinFullTypeName}?.orDefault(): ${type.kotlinTypeNameWithPackage} = this ?: ${type.kotlinFullTypeName}.defaultInstance") } @@ -355,8 +362,9 @@ public open class CodeGenerator( } line() + if (type.deprecated) line("@Suppress(\"DEPRECATION\")") line("private fun ${type.kotlinFullTypeName}.protoMergeImpl(plus: pbandk.Message?): ${type.kotlinFullTypeName} = (plus as? ${type.kotlinFullTypeName})?.let {").indented { - if (type.sortedStandardFieldsWithOneOfs().any { it.first.options.deprecated == true }) { + if (!type.deprecated && type.sortedStandardFieldsWithOneOfs().any { it.first.options.deprecated == true }) { line("@Suppress(\"DEPRECATION\")") } line("it.copy(").indented { @@ -376,7 +384,8 @@ public open class CodeGenerator( protected fun writeMessageDecodeWithExtension(type: File.Type.Message) { val lineStr = "private fun ${type.kotlinFullTypeName}.Companion." + "decodeWithImpl(u: pbandk.MessageDecoder): ${type.kotlinFullTypeName} {" - line().line("@Suppress(\"UNCHECKED_CAST\")").line(lineStr).indented { + val suppressAnnotation = if (type.deprecated) "@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")" else "@Suppress(\"UNCHECKED_CAST\")" + line().line(suppressAnnotation).line(lineStr).indented { // A bunch of locals for each field, initialized with defaults val doneKotlinFields = type.fields.map { when (it) { diff --git a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/File.kt b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/File.kt index bd690f7f..255d7dcd 100644 --- a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/File.kt +++ b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/File.kt @@ -44,7 +44,8 @@ public data class File( val mapEntry: Boolean, override val kotlinTypeName: String, override val kotlinFullTypeName: String, - val extensionRange: List = emptyList() + val extensionRange: List = emptyList(), + val deprecated: Boolean = false ) : Type() public data class Enum( @@ -52,9 +53,10 @@ public data class File( override val fullName: String, val values: List, override val kotlinTypeName: String, - override val kotlinFullTypeName: String + override val kotlinFullTypeName: String, + val deprecated: Boolean = false ) : Type() { - public data class Value(val number: Int, val name: String, val kotlinValueTypeName: String) + public data class Value(val number: Int, val name: String, val kotlinValueTypeName: String, val deprecated: Boolean = false) } } diff --git a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/FileBuilder.kt b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/FileBuilder.kt index 1a46c213..045a8740 100644 --- a/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/FileBuilder.kt +++ b/protoc-gen-pbandk/lib/src/commonMain/kotlin/pbandk/gen/FileBuilder.kt @@ -57,11 +57,13 @@ internal open class FileBuilder(val namer: Namer = Namer.Standard, val supportMa kotlinValueTypeName = namer.newEnumValueTypeName( enumDesc.name!!, value.name!!, - values.map { it.kotlinValueTypeName }) + values.map { it.kotlinValueTypeName }), + deprecated = value.options?.deprecated == true ) }, kotlinTypeName = kotlinTypeName, kotlinFullTypeName = parentKotlinFullName?.let { "${it}." }.orEmpty() + kotlinTypeName, + deprecated = enumDesc.options?.deprecated == true ) } @@ -94,7 +96,8 @@ internal open class FileBuilder(val namer: Namer = Namer.Standard, val supportMa mapEntry = supportMaps && msgDesc.options?.mapEntry == true, kotlinTypeName = kotlinTypeName, kotlinFullTypeName = kotlinFullTypeName, - extensionRange = msgDesc.extensionRange + extensionRange = msgDesc.extensionRange, + deprecated = msgDesc.options?.deprecated == true ) } diff --git a/protoc-gen-pbandk/lib/src/jvmTest/kotlin/CodeGeneratorTest.kt b/protoc-gen-pbandk/lib/src/jvmTest/kotlin/CodeGeneratorTest.kt index b98b3005..2235e0d9 100644 --- a/protoc-gen-pbandk/lib/src/jvmTest/kotlin/CodeGeneratorTest.kt +++ b/protoc-gen-pbandk/lib/src/jvmTest/kotlin/CodeGeneratorTest.kt @@ -48,6 +48,39 @@ class CodeGeneratorTest { assertTrue(deprecatedField.hasAnnotation()) } + @Test + fun testDeprecatedMessage() { + val result = compileProto("options.proto") + + assertEquals(ExitCode.OK, result.exitCode, result.messages) + + val deprecatedMessageClazz = result.classLoader.loadClass("foobar.DeprecatedMessage").kotlin + assertTrue(deprecatedMessageClazz.hasAnnotation()) + } + + @Test + fun testDeprecatedEnum() { + val result = compileProto("options.proto") + + assertEquals(ExitCode.OK, result.exitCode, result.messages) + + val deprecatedEnumClazz = result.classLoader.loadClass("foobar.DeprecatedEnum").kotlin + assertTrue(deprecatedEnumClazz.hasAnnotation()) + } + + @Test + fun testDeprecatedEnumValue() { + val result = compileProto("options.proto") + + assertEquals(ExitCode.OK, result.exitCode, result.messages) + + val enumClazz = result.classLoader.loadClass("foobar.EnumWithDeprecatedValue").kotlin + val deprecatedValue = enumClazz.sealedSubclasses.single { it.simpleName == "OLD" } + assertTrue(deprecatedValue.hasAnnotation()) + val newValue = enumClazz.sealedSubclasses.single { it.simpleName == "NEW" } + assertFalse(newValue.hasAnnotation()) + } + @Test fun testOneOf_SameNameField() { val result = compileProto("oneof_same_name.proto") diff --git a/protoc-gen-pbandk/lib/src/jvmTest/resources/protos/options.proto b/protoc-gen-pbandk/lib/src/jvmTest/resources/protos/options.proto index 9c4b418a..bd409838 100644 --- a/protoc-gen-pbandk/lib/src/jvmTest/resources/protos/options.proto +++ b/protoc-gen-pbandk/lib/src/jvmTest/resources/protos/options.proto @@ -3,4 +3,21 @@ package foobar; message Foo { int32 deprecated_field = 1 [deprecated = true]; +} + +message DeprecatedMessage { + option deprecated = true; + string value = 1; +} + +enum DeprecatedEnum { + option deprecated = true; + DEPRECATED_ENUM_UNSPECIFIED = 0; + DEPRECATED_ENUM_VALUE = 1; +} + +enum EnumWithDeprecatedValue { + ENUM_WITH_DEPRECATED_VALUE_UNSPECIFIED = 0; + ENUM_WITH_DEPRECATED_VALUE_OLD = 1 [deprecated = true]; + ENUM_WITH_DEPRECATED_VALUE_NEW = 2; } \ No newline at end of file