From ef5e0aaec6dce1b0f69d3dcd29f5205c1901e81e Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sun, 15 Feb 2026 23:27:03 +0100 Subject: [PATCH 01/14] Add naming strategy. Set default naming strategy to Kotlin idiomatic. Extend name conflict resolution to classes and files. --- gradle/libs.versions.toml | 4 + kmp-grpc-plugin/build.gradle.kts | 2 + .../plugin/GenerateKmpGrpcSourcesTask.kt | 5 +- .../kmpgrpc/plugin/KmpGrpcExtension.kt | 4 + .../timortel/kmpgrpc/plugin/NamingStrategy.kt | 15 + .../GrpcProtobufConfiguration.kt | 1 + .../sourcegeneration/ProtoSourceGenerator.kt | 17 +- .../sourcegeneration/constants/Const.kt | 2 +- .../MessageConstructorCallWriter.kt | 6 +- .../generators/dsl/ActualProtoDslWriter.kt | 8 +- .../generators/dsl/ProtoDslWriter.kt | 10 +- .../extensions/ProtoExtensionWriter.kt | 2 +- .../field/map/ActualProtoMapFieldWriter.kt | 2 +- .../field/map/ProtoMapFieldWriter.kt | 2 +- .../ActualRepeatedProtoFieldWriter.kt | 4 +- .../repeated/RepeatedProtoFieldWriter.kt | 2 +- .../ActualSingularProtoFieldWriter.kt | 6 +- .../singular/SingularProtoFieldWriter.kt | 4 +- .../FieldPropertyConstructorExtension.kt | 14 +- .../extensions/IsInitializedFieldExtension.kt | 8 +- .../functions/CopyFunctionExtension.kt | 6 +- .../functions/EqualsFunctionExtension.kt | 6 +- .../functions/HashCodeFunctionExtension.kt | 2 +- .../functions/ToStringFunctionExtension.kt | 4 +- .../DeserializationFunctionExtension.kt | 30 +- .../RequiredSizePropertyExtension.kt | 20 +- .../SerializationFunctionExtension.kt | 18 +- .../protofile/oneof/ActualProtoOneOfWriter.kt | 2 +- .../protofile/oneof/ProtoOneOfWriter.kt | 8 +- .../generators/service/ProtoServiceWriter.kt | 2 +- .../model/BaseDeclarationResolver.kt | 4 +- .../model/CodeNameResolver.kt | 35 +++ .../model/DeclarationResolver.kt | 8 +- .../model/ProtoExtensionDefinition.kt | 12 +- .../sourcegeneration/model/ProtoProject.kt | 28 +- .../model/SourceCodeNamedNode.kt | 62 ++++ .../model/declaration/ProtoBaseDeclaration.kt | 23 +- .../model/declaration/ProtoChildProperty.kt | 29 +- .../ProtoChildPropertyNameResolver.kt | 35 --- .../model/declaration/ProtoEnum.kt | 14 +- .../model/declaration/ProtoMessage.kt | 31 +- ...ration.kt => ProtoStructureDeclaration.kt} | 11 +- .../declaration/enumeration/ProtoEnumField.kt | 17 +- .../message/OneOfSealedClassNameHolder.kt | 18 ++ .../message/ProtoMessageProperty.kt | 6 +- .../model/declaration/message/ProtoOneOf.kt | 18 +- .../message/field/ProtoExtensionField.kt | 22 ++ .../message/field/ProtoMapField.kt | 8 +- .../message/field/ProtoMessageField.kt | 39 +-- .../message/field/ProtoOneOfField.kt | 23 +- .../sourcegeneration/model/file/ProtoFile.kt | 52 +++- .../model/service/ProtoRpc.kt | 19 +- .../model/service/ProtoService.kt | 15 +- .../model/structure/ProtoPackage.kt | 11 +- .../sourcegeneration/model/type/ProtoType.kt | 4 +- .../DeprecatedOptionGenerationTest.kt | 4 +- .../NestInFileClassGenerationTest.kt | 16 +- .../generation/VisibilityTest.kt | 4 +- .../modeltree/BaseModelTreeTest.kt | 72 ++++- .../modeltree/NamingStrategyTest.kt | 280 ++++++++++++++++++ .../validation/BaseValidationTest.kt | 4 +- 61 files changed, 876 insertions(+), 264 deletions(-) create mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt create mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt create mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt delete mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildPropertyNameResolver.kt rename kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/{ProtoDeclaration.kt => ProtoStructureDeclaration.kt} (87%) create mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt create mode 100644 kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoExtensionField.kt create mode 100644 kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/NamingStrategyTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e976043c..04af1f91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,8 @@ buildConfigPlugin = "5.7.1" kotlinPoet = "2.2.0" +textCaseConverter = "2.0.0" + # Misc (Testing, Example Apps) protobufJvm = "3.25.6" protobufGradlePlugin = "0.9.5" @@ -65,6 +67,8 @@ ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } squareup-kotlinpoet = { group = "com.squareup", name = "kotlinpoet", version.ref = "kotlinPoet" } squareup-okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +text-case-converter = { group = "dev.turingcomplete", name = "text-case-converter", version.ref = "textCaseConverter" } + kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } diff --git a/kmp-grpc-plugin/build.gradle.kts b/kmp-grpc-plugin/build.gradle.kts index 387b40e3..6b677200 100644 --- a/kmp-grpc-plugin/build.gradle.kts +++ b/kmp-grpc-plugin/build.gradle.kts @@ -70,6 +70,8 @@ dependencies { implementation(libs.squareup.kotlinpoet) compileOnly(libs.kotlin.gradle.plugin) + implementation(libs.text.case.converter) + testImplementation(platform(libs.junit.bom)) testImplementation(libs.junit.jupiter) testRuntimeOnly(libs.junit.platform.launcher) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt index acafc234..6bfe36f4 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt @@ -46,6 +46,9 @@ abstract class GenerateKmpGrpcSourcesTask : DefaultTask() { @get:Input abstract val skipWellKnownExtensions: Property + @get:Input + abstract val namingStrategy: Property + @get:InputDirectory abstract val generatedSourcesOutputFolder: DirectoryProperty @@ -100,6 +103,7 @@ abstract class GenerateKmpGrpcSourcesTask : DefaultTask() { logger = logger, protoFolders = protoFolders, shouldGenerateTargetMap = shouldGenerateTargetMap, + namingStrategy = namingStrategy.get(), commonOutputFolder = getCommonOutputFolder(outputFolder), jvmOutputFolder = getJVMOutputFolder(outputFolder), jsOutputFolder = getJSOutputFolder(outputFolder), @@ -108,7 +112,6 @@ abstract class GenerateKmpGrpcSourcesTask : DefaultTask() { internalVisibility = internalVisibility.get() ) - if (includeWellKnownTypes.get() && skipWellKnownExtensions.get()) { copyWellKnownExtensions() } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt index 466e3112..58e5cf14 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt @@ -43,6 +43,10 @@ open class KmpGrpcExtension @Inject constructor(objects: ObjectFactory) { .property(Boolean::class.java) .convention(false) + val namingStrategy: Property = objects + .property(NamingStrategy::class.java) + .convention(NamingStrategy.KOTLIN_IDIOMATIC) + fun common(targets: List = listOf("commonMain")) { targetSourcesMap.put(COMMON, targets) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt new file mode 100644 index 00000000..bae0720e --- /dev/null +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt @@ -0,0 +1,15 @@ +package io.github.timortel.kmpgrpc.plugin + +enum class NamingStrategy { + /** + * Keeps names exactly as defined in the .proto file. + * Example: message_name -> message_name, field_name -> field_name + */ + PROTO_LITERAL, + + /** + * Transforms names to follow Kotlin conventions. + * Example: message_name -> MessageName, field_name -> fieldName + */ + KOTLIN_IDIOMATIC +} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/configuration/GrpcProtobufConfiguration.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/configuration/GrpcProtobufConfiguration.kt index 60d09bbe..92ee8833 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/configuration/GrpcProtobufConfiguration.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/configuration/GrpcProtobufConfiguration.kt @@ -33,6 +33,7 @@ object GrpcProtobufConfiguration { it.targetSourcesMap.set(kmpGrpcExtension.targetSourcesMap.get().toMap()) it.includeWellKnownTypes.set(kmpGrpcExtension.includeWellKnownTypes.get()) it.internalVisibility.set(kmpGrpcExtension.internalVisibility.get()) + it.namingStrategy.set(kmpGrpcExtension.namingStrategy.get()) } project.plugins.withType(KotlinMultiplatformPluginWrapper::class.java) { diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/ProtoSourceGenerator.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/ProtoSourceGenerator.kt index cadafe13..c28e3810 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/ProtoSourceGenerator.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/ProtoSourceGenerator.kt @@ -8,6 +8,7 @@ import io.github.timortel.kmpgrpc.anltr.Protobuf3Parser import io.github.timortel.kmpgrpc.anltr.ProtobufEditionsLexer import io.github.timortel.kmpgrpc.anltr.ProtobufEditionsParser import io.github.timortel.kmpgrpc.plugin.KmpGrpcExtension +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.generators.project.CommonProtoProjectWriter import io.github.timortel.kmpgrpc.plugin.sourcegeneration.generators.project.NativeProtoProjectWriter import io.github.timortel.kmpgrpc.plugin.sourcegeneration.generators.project.JsProtoProjectWriter @@ -39,6 +40,7 @@ object ProtoSourceGenerator { protoFolders: List, shouldGenerateTargetMap: Map, internalVisibility: Boolean, + namingStrategy: NamingStrategy, commonOutputFolder: File, jvmOutputFolder: File, jsOutputFolder: File, @@ -59,7 +61,8 @@ object ProtoSourceGenerator { logger = logger, protoFolders = protoFolders, shouldGenerateTargetMap = shouldGenerateTargetMapBySourceTarget, - internalVisibility = internalVisibility + internalVisibility = internalVisibility, + namingStrategy = namingStrategy ) fileMap[SourceTarget.Common].writeTo(commonOutputFolder) @@ -83,12 +86,14 @@ object ProtoSourceGenerator { logger: Logger, protoFolders: List, shouldGenerateTargetMap: Map, - internalVisibility: Boolean + internalVisibility: Boolean, + namingStrategy: NamingStrategy ): Map> { val project = buildProtoProject( logger = logger, protoFolders = protoFolders, - internalVisibility = internalVisibility + internalVisibility = internalVisibility, + namingStrategy = namingStrategy ) // Before generating code, validate and print warnings / throw errors @@ -116,7 +121,8 @@ object ProtoSourceGenerator { internal fun buildProtoProject( logger: Logger, protoFolders: List, - internalVisibility: Boolean + internalVisibility: Boolean, + namingStrategy: NamingStrategy ): ProtoProject { val folders = protoFolders.mapNotNull { sourceFolder -> walkFolder(sourceFolder, logger) @@ -128,7 +134,8 @@ object ProtoSourceGenerator { ProtoFolder(name = "", folders = l.folders + r.folders, files = l.files + r.files) }, logger = logger, - defaultVisibility = if (internalVisibility) Visibility.INTERNAL else Visibility.PUBLIC + defaultVisibility = if (internalVisibility) Visibility.INTERNAL else Visibility.PUBLIC, + namingStrategy = namingStrategy ) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/constants/Const.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/constants/Const.kt index 11d4b24c..9d7da139 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/constants/Const.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/constants/Const.kt @@ -54,7 +54,7 @@ object Const { } object OneOf { - val reservedAttributeNames = setOf("requiredSize") + val reservedClassNames = setOf("NotSet") const val REQUIRED_SIZE_PROPERTY_NAME = "requiredSize" val isInitializedProperty = Property.of("isInitialized", BOOLEAN) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/MessageConstructorCallWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/MessageConstructorCallWriter.kt index 1d329f31..7853c9dd 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/MessageConstructorCallWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/MessageConstructorCallWriter.kt @@ -42,17 +42,17 @@ object MessageConstructorCallWriter { val separator = ",\n" val fields = message.fields.joinToCodeBlock(separator) { field -> - add("%N = ", field.attributeName) + add("%N = ", field.codeName) add(getFieldParameter(field)) } val mapFields = message.mapFields.joinToCodeBlock(separator) { field -> - add("%N = ", field.attributeName) + add("%N = ", field.codeName) add(getMapFieldParameter(field)) } val oneOfFields = message.oneOfs.joinToCodeBlock(separator) { oneOf -> - add("%N = ", oneOf.attributeName) + add("%N = ", oneOf.codeName) add(getOneOfFieldParameter(oneOf)) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ActualProtoDslWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ActualProtoDslWriter.kt index 81cd18f0..6b5f089d 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ActualProtoDslWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ActualProtoDslWriter.kt @@ -20,19 +20,19 @@ object ActualProtoDslWriter : ProtoDslWriter(true) { type = MessageConstructorCallWriter.ConstructorType.BUILD_PARTIAL, getFieldParameter = { field -> if (field.isConstructorParameterNullable(ProtoMessageField.ConstructorParameterType.CREATE_PARTIAL)) { - CodeBlock.of("%N", field.attributeName) + CodeBlock.of("%N", field.codeName) } else { CodeBlock.builder() - .add("%N ?: ", field.attributeName) + .add("%N ?: ", field.codeName) .add(field.defaultValue()) .build() } }, getMapFieldParameter = { field -> - CodeBlock.of("%N ?: emptyMap()", field.attributeName) + CodeBlock.of("%N ?: emptyMap()", field.codeName) }, getOneOfFieldParameter = { oneOf -> - CodeBlock.of("%N", oneOf.attributeName) + CodeBlock.of("%N", oneOf.codeName) }, getUnknownFieldsParameter = { null }, getExtensionParameter = { CodeBlock.of("%N.build()", Const.DSL.MessageExtensions.name) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ProtoDslWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ProtoDslWriter.kt index 3006962c..6a8e4aae 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ProtoDslWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/dsl/ProtoDslWriter.kt @@ -14,7 +14,7 @@ abstract class ProtoDslWriter(private val isActual: Boolean) { fun generateDslBuilderFile(protoFile: ProtoFile): FileSpec { val builder = FileSpec - .builder(protoFile.javaPackage, protoFile.javaFileName + "Dsl") + .builder(protoFile.dslFile.className) .addAnnotation(DefaultAnnotations.SuppressDeprecation) .addAnnotation(DefaultAnnotations.OptIntoKmpGrpcInternalApi) @@ -70,7 +70,7 @@ abstract class ProtoDslWriter(private val isActual: Boolean) { addProperty( PropertySpec .builder( - field.attributeName, + field.codeName, field.type.resolve().copy(nullable = true) ) .addModifiers(modifier) @@ -88,7 +88,7 @@ abstract class ProtoDslWriter(private val isActual: Boolean) { addProperty( PropertySpec .builder( - field.attributeName, + field.codeName, MUTABLE_LIST.parameterizedBy(field.type.resolve()) ).apply { if (isActual) { @@ -106,7 +106,7 @@ abstract class ProtoDslWriter(private val isActual: Boolean) { addProperty( PropertySpec .builder( - name = field.attributeName, + name = field.codeName, type = MUTABLE_MAP.parameterizedBy( field.keyType.resolve(), field.valuesType.resolve() @@ -126,7 +126,7 @@ abstract class ProtoDslWriter(private val isActual: Boolean) { addProperty( PropertySpec .builder( - oneOf.attributeName, + oneOf.codeName, oneOf.sealedClassName, modifier ) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/extensions/ProtoExtensionWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/extensions/ProtoExtensionWriter.kt index d27af60a..bad8b343 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/extensions/ProtoExtensionWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/extensions/ProtoExtensionWriter.kt @@ -44,7 +44,7 @@ object ProtoExtensionWriter { addProperty( PropertySpec.builder( - name = field.name, + name = field.codeName, type = propertyType, modifiers = if (isActual) listOf(KModifier.ACTUAL) else emptyList() ) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ActualProtoMapFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ActualProtoMapFieldWriter.kt index 60f4a4f4..92ed11e2 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ActualProtoMapFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ActualProtoMapFieldWriter.kt @@ -10,7 +10,7 @@ object ActualProtoMapFieldWriter : ProtoMapFieldWriter() { builder: PropertySpec.Builder, field: ProtoMapField ) { - builder.initializer(field.attributeName) + builder.initializer(field.codeName) builder.addModifiers(KModifier.ACTUAL) } } \ No newline at end of file diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ProtoMapFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ProtoMapFieldWriter.kt index 2736258f..919044f4 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ProtoMapFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/map/ProtoMapFieldWriter.kt @@ -13,7 +13,7 @@ abstract class ProtoMapFieldWriter : BaseProtoFieldWriter { builder.addProperty( PropertySpec .builder( - field.attributeName, + field.codeName, MAP.parameterizedBy(field.keyType.resolve(), field.valuesType.resolve()) ) .addKdoc(field.infoText) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/ActualRepeatedProtoFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/ActualRepeatedProtoFieldWriter.kt index 6fcc5415..b03f747e 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/ActualRepeatedProtoFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/ActualRepeatedProtoFieldWriter.kt @@ -8,10 +8,10 @@ object ActualRepeatedProtoFieldWriter : RepeatedProtoFieldWriter() { override val attrs: List = listOf(KModifier.ACTUAL) override fun modifyListProperty(builder: PropertySpec.Builder, field: ProtoMessageField) { - builder.initializer("%N", field.attributeName) + builder.initializer("%N", field.codeName) } override fun modifyCountProperty(builder: PropertySpec.Builder, field: ProtoMessageField) { - builder.initializer("%N.size", field.attributeName) + builder.initializer("%N.size", field.codeName) } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/RepeatedProtoFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/RepeatedProtoFieldWriter.kt index 4fb23015..be8392a6 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/RepeatedProtoFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/repeated/RepeatedProtoFieldWriter.kt @@ -12,7 +12,7 @@ abstract class RepeatedProtoFieldWriter : BaseProtoFieldWriter { fun addField(builder: TypeSpec.Builder, field: ProtoMessageField) { val listProperty = PropertySpec .builder( - field.attributeName, + field.codeName, LIST.parameterizedBy(field.type.resolve()) ) .addKdoc(field.infoText) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/ActualSingularProtoFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/ActualSingularProtoFieldWriter.kt index cbdb2b09..7e5b699a 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/ActualSingularProtoFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/ActualSingularProtoFieldWriter.kt @@ -14,17 +14,17 @@ object ActualSingularProtoFieldWriter : SingularProtoFieldWriter() { CodeBlock .builder() .apply { - add("%N ?: ", field.attributeName) + add("%N ?: ", field.codeName) add(field.defaultValue(messageDefaultValue = ProtoType.MessageDefaultValue.EMPTY)) } .build() ) } else { - initializer(field.attributeName) + initializer(field.codeName) } } override fun PropertySpec.Builder.modifyIsSetProperty(field: ProtoMessageField) { - initializer("%N != null", field.attributeName) + initializer("%N != null", field.codeName) } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/SingularProtoFieldWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/SingularProtoFieldWriter.kt index ffd981b0..21fe29c3 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/SingularProtoFieldWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/field/singular/SingularProtoFieldWriter.kt @@ -15,7 +15,7 @@ abstract class SingularProtoFieldWriter : BaseProtoFieldWriter { with(builder) { addProperty( PropertySpec - .builder(field.attributeName, field.type.resolve()) + .builder(field.codeName, field.type.resolve()) .addKdoc(field.infoText) .addModifiers(attrs) .apply { @@ -29,7 +29,7 @@ abstract class SingularProtoFieldWriter : BaseProtoFieldWriter { if (field.needsIsSetProperty) { addProperty( PropertySpec - .builder(field.isSetProperty.attributeName, BOOLEAN) + .builder(field.isSetProperty.codeName, BOOLEAN) .addKdoc(field.infoText) .addModifiers(attrs) .apply { modifyIsSetProperty(field) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/FieldPropertyConstructorExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/FieldPropertyConstructorExtension.kt index 33ee23c5..ed06c15f 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/FieldPropertyConstructorExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/FieldPropertyConstructorExtension.kt @@ -92,9 +92,9 @@ object FieldPropertyConstructorExtension : MessageWriterExtension { MessageConstructorCallWriter.getConstructorCallCode( message = message, type = MessageConstructorCallWriter.ConstructorType.DIRECT, - getFieldParameter = { field -> CodeBlock.of("%N", field.attributeName) }, - getMapFieldParameter = { field -> CodeBlock.of("%N", field.attributeName) }, - getOneOfFieldParameter = { field -> CodeBlock.of("%N", field.attributeName) }, + getFieldParameter = { field -> CodeBlock.of("%N", field.codeName) }, + getMapFieldParameter = { field -> CodeBlock.of("%N", field.codeName) }, + getOneOfFieldParameter = { field -> CodeBlock.of("%N", field.codeName) }, getUnknownFieldsParameter = { CodeBlock.of( "%N", @@ -139,7 +139,7 @@ object FieldPropertyConstructorExtension : MessageWriterExtension { builder.addParameter( ParameterSpec - .builder(field.attributeName, type) + .builder(field.codeName, type) .apply { if (!isActual && addDefaultValues) { defaultValue( @@ -161,7 +161,7 @@ object FieldPropertyConstructorExtension : MessageWriterExtension { ProtoFieldCardinality.Repeated -> { builder.addParameter( ParameterSpec - .builder(field.attributeName, LIST.parameterizedBy(field.type.resolve())) + .builder(field.codeName, LIST.parameterizedBy(field.type.resolve())) .apply { if (!isActual && addDefaultValues) defaultValue("emptyList()") } @@ -175,7 +175,7 @@ object FieldPropertyConstructorExtension : MessageWriterExtension { builder.addParameter( ParameterSpec .builder( - mapField.attributeName, + mapField.codeName, MAP.parameterizedBy( mapField.keyType.resolve(), mapField.valuesType.resolve(), @@ -192,7 +192,7 @@ object FieldPropertyConstructorExtension : MessageWriterExtension { builder.addParameter( ParameterSpec .builder( - oneOf.attributeName, + oneOf.codeName, oneOf.sealedClassName ) .apply { diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt index 6ac021fc..251cfe28 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt @@ -47,14 +47,14 @@ object IsInitializedFieldExtension : MessageWriterExtension { is ProtoFieldCardinality.Singular -> { add( "(%1N == null || %1N.%2N)", - it.attributeName, + it.codeName, Const.Message.isInitializedProperty.name ) } ProtoFieldCardinality.Repeated -> { add( "%N.all { it.%N }", - it.attributeName, + it.codeName, Const.Message.isInitializedProperty.name ) } @@ -64,7 +64,7 @@ object IsInitializedFieldExtension : MessageWriterExtension { val subMessageOneOfFieldsBool = oneOfs.joinToCodeBlock(separator) { add( "%N.%N", - it.attributeName, + it.codeName, Const.Message.OneOf.isInitializedProperty.name ) } @@ -72,7 +72,7 @@ object IsInitializedFieldExtension : MessageWriterExtension { val subMessageMapFieldsBool = subMessageMapFields.joinToCodeBlock(separator) { add( "%N.values.all { it.%N }", - it.attributeName, + it.codeName, Const.Message.isInitializedProperty.name ) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/CopyFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/CopyFunctionExtension.kt index bf1cf6cf..e10e8c6e 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/CopyFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/CopyFunctionExtension.kt @@ -22,11 +22,11 @@ object CopyFunctionExtension : MessageWriterExtension { .forEach { messageProperty -> addParameter( ParameterSpec.builder( - name = messageProperty.attributeName, + name = messageProperty.codeName, type = messageProperty.propertyType ) .apply { - if (!isActual) defaultValue("this.%N", messageProperty.attributeName) + if (!isActual) defaultValue("this.%N", messageProperty.codeName) } .build() ) @@ -70,7 +70,7 @@ object CopyFunctionExtension : MessageWriterExtension { .apply { (message.fields + message.mapFields + message.oneOfs) .forEach { messageProperty -> - add("%1N = %1N,\n", messageProperty.attributeName) + add("%1N = %1N,\n", messageProperty.codeName) } if (message.isExtendable) { diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/EqualsFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/EqualsFunctionExtension.kt index 2fd68a40..52f3a188 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/EqualsFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/EqualsFunctionExtension.kt @@ -50,7 +50,7 @@ object EqualsFunctionExtension : MessageWriterExtension { add("if (") add( field.type.inequalityCode( - attributeName = field.attributeName, + attributeName = field.codeName, otherParamName = otherParamName, isRepeated = field.cardinality == ProtoFieldCardinality.Repeated ) @@ -61,7 +61,7 @@ object EqualsFunctionExtension : MessageWriterExtension { val mapFieldCodeBlock = message.mapFields.joinToCodeBlock(separator) { mapField -> add( "if (%1N != %2N.%1N) return false", - mapField.attributeName, + mapField.codeName, otherParamName ) } @@ -70,7 +70,7 @@ object EqualsFunctionExtension : MessageWriterExtension { val oneOfCodeBlock = message.oneOfs.joinToCodeBlock(separator) { oneOf -> add( "if (%1N != %2N.%1N) return false", - oneOf.attributeName, + oneOf.codeName, otherParamName ) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/HashCodeFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/HashCodeFunctionExtension.kt index 50b8ee15..e031c667 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/HashCodeFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/HashCodeFunctionExtension.kt @@ -45,7 +45,7 @@ object HashCodeFunctionExtension : MessageWriterExtension { } properties.forEachIndexed { index, property -> - val attrName = property.attributeName + val attrName = property.codeName // Mimic the way IntelliJ generates hashCode if (index == 0) { diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/ToStringFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/ToStringFunctionExtension.kt index f5683ac6..1f43e4c5 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/ToStringFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/functions/ToStringFunctionExtension.kt @@ -27,9 +27,9 @@ object ToStringFunctionExtension : MessageWriterExtension { val propertiesCodeBlock = (message.fields + message.oneOfs + message.mapFields).joinToCodeBlock(separator) { field -> - addStatement("append(%S)", field.attributeName) + addStatement("append(%S)", field.codeName) addStatement("append(%S)", "=") - addStatement("append(%N.toString())", field.attributeName) + addStatement("append(%N.toString())", field.codeName) } val unknownFieldsCodeBlock = CodeBlock.builder() diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/DeserializationFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/DeserializationFunctionExtension.kt index aa96d509..c28868ca 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/DeserializationFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/DeserializationFunctionExtension.kt @@ -128,7 +128,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { type = field.type, file = field.file, fieldNumber = field.number, - variableName = oneOf.attributeName, + variableName = oneOf.codeName, assignMode = "=", constructType = { add("%T(", field.sealedClassChildName) @@ -231,7 +231,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { type = field.type, file = field.file, fieldNumber = field.number, - variableName = field.attributeName, + variableName = field.codeName, assignMode = "=", constructType = { it() } ) @@ -246,7 +246,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { field.type is ProtoType.DefType && field.type.isMessage -> { val message = field.type.resolveDeclaration() as ProtoMessage - addCode("%N·+=·", field.attributeName) + addCode("%N·+=·", field.codeName) addCode(buildReadScalarFieldMessageTypeCode(field.type, message, field.number)) addCode("\n") } @@ -272,7 +272,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { type = field.type, file = field.file, fieldNumber = field.number, - variableName = field.attributeName, + variableName = field.codeName, assignMode = "+=", constructType = { it() } ) @@ -282,7 +282,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { addStatement( "%N·+=·%N.%N()", - field.attributeName, + field.codeName, wrapperParamName, functionName ) @@ -301,7 +301,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { type = field.type, file = field.file, fieldNumber = field.number, - variableName = field.attributeName, + variableName = field.codeName, assignMode = "+=", constructType = { it() } ) @@ -309,7 +309,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { } else { addCode( "%N·+=·%N.%N()\n", - field.attributeName, + field.codeName, wrapperParamName, getReadScalarFunctionName(field.type) ) @@ -333,7 +333,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { readMapEntry, wrapperParamName, mapField.number, - mapField.attributeName, + mapField.codeName, Const.Message.Companion.WrapperDeserializationFunction.UNKNOWN_FIELDS_LOCAL_VARIABLE, DataType::class.asTypeName(), mapField.keyType.wireType.name, @@ -371,9 +371,9 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { MessageConstructorCallWriter.getConstructorCallCode( message = message, type = MessageConstructorCallWriter.ConstructorType.BUILD_PARTIAL, - getFieldParameter = { CodeBlock.of("%N", it.attributeName) }, - getMapFieldParameter = { CodeBlock.of("%N", it.attributeName) }, - getOneOfFieldParameter = { CodeBlock.of("%N", it.attributeName) }, + getFieldParameter = { CodeBlock.of("%N", it.codeName) }, + getMapFieldParameter = { CodeBlock.of("%N", it.codeName) }, + getOneOfFieldParameter = { CodeBlock.of("%N", it.codeName) }, getUnknownFieldsParameter = { CodeBlock.of( "%N", @@ -405,7 +405,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { declareLocalVariable( builder, - field.attributeName, + field.codeName, type, true, defaultValue @@ -415,7 +415,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { ProtoFieldCardinality.Repeated -> { declareLocalVariable( builder, - field.attributeName, + field.codeName, MUTABLE_LIST.parameterizedBy(field.type.resolve()), false, CodeBlock.of("mutableListOf()") @@ -427,7 +427,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { message.mapFields.forEach { field -> declareLocalVariable( builder, - field.attributeName, + field.codeName, MUTABLE_MAP.parameterizedBy(field.keyType.resolve(), field.valuesType.resolve()), false, CodeBlock.of("mutableMapOf()") @@ -437,7 +437,7 @@ class DeserializationFunctionExtension : BaseSerializationExtension() { message.oneOfs.forEach { oneOf -> declareLocalVariable( builder, - oneOf.attributeName, + oneOf.codeName, oneOf.sealedClassName, true, CodeBlock.of("%T", oneOf.sealedClassNameNotSet) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/RequiredSizePropertyExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/RequiredSizePropertyExtension.kt index 86545b3c..f8409010 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/RequiredSizePropertyExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/RequiredSizePropertyExtension.kt @@ -38,7 +38,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { val fieldsCodeBlock = message.fields.joinToCodeBlock(separator) { field -> when { field.needsIsSetProperty -> { - add("if·(%N != null)·{·", field.attributeName) + add("if·(%N != null)·{·", field.codeName) add( getCodeForRequiredSizeForScalarAttributeC( field = field, @@ -59,7 +59,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { field.cardinality.isImplicit -> { add("if·(") - add(field.type.isDefaultValueCode(field.attributeName, false)) + add(field.type.isDefaultValueCode(field.codeName, false)) add(")·0·else·{ ") add(getCodeForRequiredSizeForScalarAttributeC(field, isOptional = false)) add(" }") @@ -67,10 +67,10 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { field.cardinality == ProtoFieldCardinality.Repeated -> { add("if·(") - add(field.type.isDefaultValueCode(field.attributeName, true)) + add(field.type.isDefaultValueCode(field.codeName, true)) beginControlFlow(")·0·else·{") - beginControlFlow("%N.let·{·e·->", field.attributeName) + beginControlFlow("%N.let·{·e·->", field.codeName) beginControlFlow("val·dataSize·=·e.sumOf·{") when (field.type) { @@ -109,7 +109,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { computeInt32SizeNoTag ) } else { - addStatement("dataSize + %N.size * tagSize", field.attributeName) + addStatement("dataSize + %N.size * tagSize", field.codeName) } endControlFlow() @@ -124,7 +124,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { "%M(%L, %N, ::%M, ", computeMapSize, mapField.number, - mapField.attributeName, + mapField.codeName, getComputeDataTypeSizeMember(mapField.keyType, true) ) @@ -136,7 +136,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { val oneOfsBlock = message.oneOfs.joinToCodeBlock(separator) { oneOf -> add( "%N.%N", - oneOf.attributeName, + oneOf.codeName, Const.Message.OneOf.REQUIRED_SIZE_PROPERTY_NAME ) } @@ -171,7 +171,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { formatString, getComputeDataTypeSizeMember(type, true), field.number, - field.attributeName + field.codeName ) } @@ -182,7 +182,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { "%M(%L, %N)", computeMessageSize, field.number, - field.attributeName + field.codeName ) } @@ -191,7 +191,7 @@ class RequiredSizePropertyExtension : BaseSerializationExtension() { "%M(%L, this.%N.%N)", computeEnumSize, field.number, - field.attributeName, + field.codeName, Const.Enum.NUMBER_PROPERTY_NAME ) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/SerializationFunctionExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/SerializationFunctionExtension.kt index 0d987fe5..42f6ccca 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/SerializationFunctionExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/serialization/SerializationFunctionExtension.kt @@ -68,7 +68,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { field.cardinality.isImplicit -> { addCode("if·(") - addCode(field.type.isNotDefaultValueCode(field.attributeName, false)) + addCode(field.type.isNotDefaultValueCode(field.codeName, false)) beginControlFlow(")·{ ") addCode( @@ -93,7 +93,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { Const.Message.SerializeFunction.STREAM_PARAM, writeArrayFunction, field.number, - field.attributeName, + field.codeName, wireFormatMakeTag(field.number, field.type.wireType, true) ) } else { @@ -108,7 +108,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { Const.Message.SerializeFunction.STREAM_PARAM, writeArrayFunction, field.number, - field.attributeName + field.codeName ) } } @@ -122,7 +122,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { message.oneOfs.forEach { oneOf -> addStatement( "%N.%N(%N)", - oneOf.attributeName, + oneOf.codeName, Const.Message.OneOf.SERIALIZE_FUNCTION_NAME, Const.Message.SerializeFunction.STREAM_PARAM ) @@ -156,7 +156,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { Const.Message.SerializeFunction.STREAM_PARAM, "writeMap", field.number, - field.attributeName + field.codeName ) addCode(getComputeMapValueRequiredSizeCode(field.keyType)) @@ -266,7 +266,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { streamParam, functionName, field.number, - field.attributeName + field.codeName ) } @@ -280,7 +280,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { streamParam, functionName, field.number, - field.attributeName + field.codeName ) } is ProtoEnum -> { @@ -288,7 +288,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { "%N.writeEnum(%L, %N.%N)\n", streamParam, field.number, - field.attributeName, + field.codeName, Const.Enum.NUMBER_PROPERTY_NAME ) } @@ -300,7 +300,7 @@ class SerializationFunctionExtension : BaseSerializationExtension() { CodeBlock.builder() .beginControlFlow( "if (%N)", - field.isSetProperty.attributeName + field.isSetProperty.codeName ) .add(serializationCodeBlock) .endControlFlow() diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ActualProtoOneOfWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ActualProtoOneOfWriter.kt index e8cc343e..bcafe48f 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ActualProtoOneOfWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ActualProtoOneOfWriter.kt @@ -72,7 +72,7 @@ abstract class ActualProtoOneOfWriter : ProtoOneOfWriter(true) { .apply { when (childClassType) { is ChildClassType.Normal -> if (childClassType.field.type.isMessage) { - initializer("%N.%N", childClassType.field.attributeName, Const.Message.isInitializedProperty.name) + initializer("%N.%N", childClassType.field.codeName, Const.Message.isInitializedProperty.name) } else { initializer("true") } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ProtoOneOfWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ProtoOneOfWriter.kt index f1bfd6c2..47d51fb6 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ProtoOneOfWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/oneof/ProtoOneOfWriter.kt @@ -17,7 +17,7 @@ abstract class ProtoOneOfWriter(private val isActual: Boolean) { builder.addProperty( PropertySpec .builder( - name = protoOneOf.attributeName, + name = protoOneOf.codeName, type = protoOneOf.sealedClassName, modifiers = attrs ) @@ -55,7 +55,7 @@ abstract class ProtoOneOfWriter(private val isActual: Boolean) { .constructorBuilder() .addParameter( ParameterSpec - .builder(field.attributeName, field.type.resolve()) + .builder(field.codeName, field.type.resolve()) .build() ) .apply { @@ -68,10 +68,10 @@ abstract class ProtoOneOfWriter(private val isActual: Boolean) { } .addProperty( PropertySpec - .builder(name = field.attributeName, type = field.type.resolve()) + .builder(name = field.codeName, type = field.type.resolve()) .apply { if (isActual) { - initializer(field.attributeName) + initializer(field.codeName) addModifiers(KModifier.ACTUAL) } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/service/ProtoServiceWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/service/ProtoServiceWriter.kt index 35e923f4..ff436130 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/service/ProtoServiceWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/service/ProtoServiceWriter.kt @@ -46,7 +46,7 @@ abstract class ProtoServiceWriter(private val isActual: Boolean) { addFunction( FunSpec - .builder(rpc.name) + .builder(rpc.codeName) .apply { if (!rpc.isReceivingStream) { this.addModifiers(KModifier.SUSPEND) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/BaseDeclarationResolver.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/BaseDeclarationResolver.kt index c914e9ef..8e55a1b2 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/BaseDeclarationResolver.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/BaseDeclarationResolver.kt @@ -1,11 +1,11 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType interface BaseDeclarationResolver { /** * @return the declarations associated with this identifier, or an empty list of none have been found. */ - fun resolveDeclaration(type: ProtoType.DefType): ProtoDeclaration? + fun resolveDeclaration(type: ProtoType.DefType): ProtoStructureDeclaration? } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt new file mode 100644 index 00000000..a82e472c --- /dev/null +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt @@ -0,0 +1,35 @@ +package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model + +/** + * Used to resolve the property name of a proto node in the generated code and to avoid name clashes. + */ +interface CodeNameResolver { + + val reservedNames: Set + + val consideredNodes: List + + fun resolveNameConflict(name: String): String = "${name}_" + + fun resolveCodeName(field: SourceCodeNamedNode): String { + val reservedNames = reservedNames.toMutableSet() + val nameMap: MutableMap = mutableMapOf() + + consideredNodes + .sortedBy { it.priority } + .forEach { currentNode -> + var attributeName = currentNode.desiredCodeName + + while (attributeName in reservedNames) { + attributeName = resolveNameConflict(attributeName) + } + + if (currentNode == field) return attributeName + + reservedNames += attributeName + nameMap[currentNode] = attributeName + } + + throw IllegalArgumentException("field=$field not child of resolver=$this. Known children=$consideredNodes.") + } +} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/DeclarationResolver.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/DeclarationResolver.kt index 1cb23df7..eda980ee 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/DeclarationResolver.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/DeclarationResolver.kt @@ -1,7 +1,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile @@ -17,7 +17,7 @@ interface DeclarationResolver : BaseDeclarationResolver { val candidates: List - override fun resolveDeclaration(type: ProtoType.DefType): ProtoDeclaration? { + override fun resolveDeclaration(type: ProtoType.DefType): ProtoStructureDeclaration? { return resolveDeclaration(type, true) } @@ -25,7 +25,7 @@ interface DeclarationResolver : BaseDeclarationResolver { * @param type the identifier of the declaration. Must not start with '.'. * @param canTryNextInnerScope if resolving may go to the parent to try the next inner scope */ - fun resolveDeclaration(type: ProtoType.DefType, canTryNextInnerScope: Boolean): ProtoDeclaration? { + fun resolveDeclaration(type: ProtoType.DefType, canTryNextInnerScope: Boolean): ProtoStructureDeclaration? { // Search scope val identifier = type.declaration @@ -115,7 +115,7 @@ interface DeclarationResolver : BaseDeclarationResolver { /** * Tries resolving the identifier in the next inner scope, which is the parent of this node */ - fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoDeclaration? + fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoStructureDeclaration? /** * @param type the identifier relevant for the current scope diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoExtensionDefinition.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoExtensionDefinition.kt index 9afba55a..6a30a9a9 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoExtensionDefinition.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoExtensionDefinition.kt @@ -2,7 +2,9 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model import com.squareup.kotlinpoet.ClassName import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildProperty import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoExtensionField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType @@ -18,16 +20,22 @@ data class ProtoExtensionDefinition( val messageType: ProtoType.DefType, val fields: List, val ctx: ParserRuleContext -) : ProtoNode { +) : ProtoNode, CodeNameResolver { lateinit var parent: Parent val file: ProtoFile get() = parent.file + override val reservedNames: Set + get() = emptySet() + + override val consideredNodes: List + get() = fields + init { messageType.parent = ProtoType.Parent.ExtensionDefinition(this) - fields.forEach { it.parent = ProtoMessageField.Parent.ExtensionDefinition(this) } + fields.forEach { it.parent = ProtoExtensionField.Parent.ExtensionDefinition(this) } } override fun validate() { diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt index 5bbc11fe..b19d6328 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt @@ -1,6 +1,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.NamingStrategy +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.structure.ProtoFolder @@ -11,13 +12,14 @@ import org.slf4j.Logger data class ProtoProject( val rootFolder: ProtoFolder, val logger: Logger, - val defaultVisibility: Visibility + val defaultVisibility: Visibility, + val namingStrategy: NamingStrategy ) : ProtoNode, ProtoExtensionDefinitionFinder { val rootPackage: ProtoPackage // This is an optimization, to avoid searching the tree for every message - private val extensionDefinitionsMap: Map> + private val extensionDefinitionsMap: Map> init { rootFolder.parent = ProtoFolder.Parent.Project(this) @@ -32,7 +34,7 @@ data class ProtoProject( rootPackage.validate() } - fun resolveDeclarationFullyQualified(type: ProtoType.DefType): ProtoDeclaration? { + fun resolveDeclarationFullyQualified(type: ProtoType.DefType): ProtoStructureDeclaration? { return rootPackage.resolveDeclaration(type) } @@ -44,6 +46,24 @@ data class ProtoProject( return extensionDefinitionsMap[message].orEmpty() } + fun getCodeNameResolverForKotlinPackage(pkg: String): CodeNameResolver { + val trimmedPkg = pkg.trim('.') + val filesInPackage = rootPackage.findFiles { it.javaPackage.trim('.') == trimmedPkg } + + val consideredNodes = filesInPackage.flatMap { file -> + val topLevelDeclarations = file.messages + file.enums + file.services + val declarations = topLevelDeclarations.filterNot { it.isNested } + val containSelf = topLevelDeclarations.any { it.isNested } || file.extensionDefinitions.any { it.fields.isNotEmpty() } + + declarations + listOf(file.dslFile) + if (containSelf) listOf(file) else emptyList() + } + + return object : CodeNameResolver { + override val reservedNames: Set = emptySet() + override val consideredNodes: List = consideredNodes + } + } + private fun buildPackageTree( files: List, parentPackages: List diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt new file mode 100644 index 00000000..6aaa41fb --- /dev/null +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt @@ -0,0 +1,62 @@ +package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model + +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.NamingStrategy + +/** + * Base interface for all proto nodes that contain a name that could be written to code + */ +interface SourceCodeNamedNode { + + val project: ProtoProject + + val kotlinIdiomaticTextCase: TextCase + + /** + * The name of the property as defined in the proto source code + */ + val name: String + + /** + * The name of the property after applying the code style transformation rules. + */ + val transformedKotlinName: String + get() = when (project.namingStrategy) { + NamingStrategy.PROTO_LITERAL -> name + NamingStrategy.KOTLIN_IDIOMATIC -> { + // Ensure the input is treated as one cohesive "word" set before converting + val normalizedName = name.trim('_') // Protobuf allows __internal__ names + + when { + // If it's pure uppercase with underscores (likely an Enum constant) + normalizedName.all { it.isUpperCase() || it == '_' || it.isDigit() } -> + StandardTextCases.SCREAMING_SNAKE_CASE.convertTo(kotlinIdiomaticTextCase, normalizedName) + + // If it has underscores, it's a variation of snake_case + normalizedName.contains('_') -> + StandardTextCases.SNAKE_CASE.convertTo(kotlinIdiomaticTextCase, normalizedName) + + // Otherwise, assume it's already some form of Camel/Pascal case + else -> StandardTextCases.PASCAL_CASE.convertTo(kotlinIdiomaticTextCase, normalizedName) + } + } + } + + /** + * The name the node would like to have if there were not any clashes + */ + val desiredCodeName: String get() = transformedKotlinName + + /** + * The name of the node in the generated code + */ + val codeName: String get() = codeNameResolver.resolveCodeName(this) + + /** + * The priority of this node in the context of name clash resolution. + */ + val priority: Int get() = 1 + + val codeNameResolver: CodeNameResolver +} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoBaseDeclaration.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoBaseDeclaration.kt index 3166456f..6bdc1c5a 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoBaseDeclaration.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoBaseDeclaration.kt @@ -1,26 +1,27 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration import com.squareup.kotlinpoet.ClassName +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoLanguageVersion import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.Options import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoVisibilityHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.ProtoNestInFileClass import org.antlr.v4.runtime.ParserRuleContext -interface ProtoBaseDeclaration : ProtoOptionsHolder, ProtoVisibilityHolder { +interface ProtoBaseDeclaration : ProtoOptionsHolder, ProtoVisibilityHolder, SourceCodeNamedNode { /** * The name of this declaration */ - val name: String + override val name: String - /** - * The name of the class generated for this declaration - */ - val kotlinClassName: String get() = name + override val kotlinIdiomaticTextCase: TextCase get() = StandardTextCases.PASCAL_CASE /** * The file this declaration is located in @@ -36,14 +37,14 @@ interface ProtoBaseDeclaration : ProtoOptionsHolder, ProtoVisibilityHolder { val className: ClassName get() { return if (isNested) { - file.className.nestedClass(kotlinClassName) + file.className.nestedClass(codeName) } else { - ClassName(file.javaPackage, kotlinClassName) + ClassName(file.javaPackage, codeName) } } /** - * If the message is nested within another class in the generated code. + * If this declaration is nested within another class in the generated code. */ val isNested: Boolean get() = when (file.languageVersion) { @@ -55,5 +56,9 @@ interface ProtoBaseDeclaration : ProtoOptionsHolder, ProtoVisibilityHolder { } } + override val codeNameResolver: CodeNameResolver + get() = if (isNested) file.declarationCodeNameResolver + else file.codeNameResolver + val ctx: ParserRuleContext } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildProperty.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildProperty.kt index 054601d2..08529f2f 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildProperty.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildProperty.kt @@ -1,34 +1,17 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration import com.squareup.kotlinpoet.TypeName +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode /** * Base interface of a proto property of a generated class. */ -interface ProtoChildProperty { +interface ProtoChildProperty : SourceCodeNamedNode { - val resolvingParent: ProtoChildPropertyNameResolver - - /** - * The name of the property as defined in the proto source code - */ - val name: String - - /** - * The name of the field the property would like to have if there were not any clashes - */ - val desiredAttributeName: String - - /** - * The name of the field in the generated message class - */ - val attributeName: String - get() = resolvingParent.resolveMessagePropertyName(this) - - /** - * The priority of this child property in the context of name clash resolution. - */ - val priority: Int + override val kotlinIdiomaticTextCase: TextCase + get() = StandardTextCases.SOFT_CAMEL_CASE /** * The type of the property in the generated code diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildPropertyNameResolver.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildPropertyNameResolver.kt deleted file mode 100644 index e599dfbb..00000000 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoChildPropertyNameResolver.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration - -/** - * Parent interface of [ProtoMessage] and [io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoOneOf]. - * - * Used to resolve the property name of a proto property in the generated code and to avoid name clashes. - */ -interface ProtoChildPropertyNameResolver { - - val reservedAttributeNames: Set - - val childProperties: List - - fun resolveMessagePropertyName(field: ProtoChildProperty): String { - val reservedNames = reservedAttributeNames.toMutableSet() - val nameMap: MutableMap = mutableMapOf() - - childProperties - .sortedBy { it.priority } - .forEach { currentField -> - var attributeName = currentField.desiredAttributeName - - while (attributeName in reservedNames) { - attributeName = "${attributeName}_" - } - - if (currentField == field) return attributeName - - reservedNames += attributeName - nameMap[currentField] = attributeName - } - - throw IllegalArgumentException("field=$field not child of resolver=$this. Known children=$childProperties.") - } -} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoEnum.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoEnum.kt index 445246df..2123d167 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoEnum.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoEnum.kt @@ -4,10 +4,12 @@ import com.squareup.kotlinpoet.ClassName import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException import io.github.timortel.kmpgrpc.plugin.sourcegeneration.Warnings import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.BaseDeclarationResolver +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoDeclParent import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoLanguageVersion import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.enumeration.ProtoEnumField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoReservation import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile @@ -25,7 +27,7 @@ data class ProtoEnum( override val reservation: ProtoReservation, override val symbolVisibility: ProtoSymbolVisibility?, override val ctx: ParserRuleContext -) : ProtoDeclaration, BaseDeclarationResolver, ProtoFieldHolder { +) : ProtoStructureDeclaration, BaseDeclarationResolver, ProtoFieldHolder, CodeNameResolver { private companion object { private const val UNRECOGNIZED_FILE_NAME = "Unrecognized" @@ -52,6 +54,12 @@ data class ProtoEnum( val unrecognizedSubtypeClassName: ClassName get() = className.nestedClass(UNRECOGNIZED_FILE_NAME) + override val consideredNodes: List + get() = fields + + override val reservedNames: Set + get() = emptySet() + init { fields.forEach { it.enum = this } } @@ -90,7 +98,7 @@ data class ProtoEnum( } override fun validate() { - super.validate() + super.validate() super.validate() if (fields.isEmpty()) throw CompilationException.EnumNoFields( @@ -134,7 +142,7 @@ data class ProtoEnum( } // An enum does not have children, so it cannot resolve anything - override fun resolveDeclaration(type: ProtoType.DefType): ProtoDeclaration? { + override fun resolveDeclaration(type: ProtoType.DefType): ProtoStructureDeclaration? { return null } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoMessage.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoMessage.kt index 356274bb..97a82861 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoMessage.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoMessage.kt @@ -7,6 +7,7 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.* import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoExtensionRanges import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoOneOf import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoReservation +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoExtensionField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMapField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile @@ -33,7 +34,7 @@ data class ProtoMessage( override val symbolVisibility: ProtoSymbolVisibility?, val type: Type, override val ctx: ParserRuleContext -) : ProtoDeclaration, FileBasedDeclarationResolver, ProtoFieldHolder, ProtoChildPropertyNameResolver, +) : ProtoStructureDeclaration, FileBasedDeclarationResolver, ProtoFieldHolder, ProtoExtensionDefinitionHolder, ProtoExtensionDefinitionFinder { override lateinit var parent: ProtoDeclParent @@ -81,17 +82,27 @@ data class ProtoMessage( val dslBuildFunction: MemberName get() = MemberName(file.javaPackage, name.decapitalize()) - override val childProperties: List - get() = fields + mapFields + oneOfs + fields.flatMap { it.childProperties } - - override val reservedAttributeNames: Set - get() = Const.Message.reservedAttributeNames - val extensionsInProject: List get() = project.findExtensionDefinitionsForMessage(this) val isExtendable: Boolean = extensionRange.ranges.isNotEmpty() + val fieldNameResolver = object : CodeNameResolver { + override val consideredNodes: List + get() = fields + mapFields + oneOfs + fields.flatMap { it.childProperties } + + override val reservedNames: Set + get() = Const.Message.reservedAttributeNames + } + + val classNameResolver = object : CodeNameResolver { + override val consideredNodes: List + get() = messages + enums + + override val reservedNames: Set + get() = setOf("Companion") + } + init { val parent = ProtoDeclParent.Message(this) @@ -99,7 +110,7 @@ data class ProtoMessage( enums.forEach { it.parent = parent } oneOfs.forEach { it.message = this } - fields.forEach { it.parent = ProtoMessageField.Parent.Message(this) } + fields.forEach { it.parent = ProtoExtensionField.Parent.Message(this) } mapFields.forEach { it.message = this } extensionDefinitions.forEach { it.parent = ProtoExtensionDefinition.Parent.Message(this) } @@ -107,7 +118,7 @@ data class ProtoMessage( extensionRange.message = this } - override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoDeclaration? { + override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoStructureDeclaration? { return when (val p = parent) { is ProtoDeclParent.File -> p.file.resolveDeclaration(type) is ProtoDeclParent.Message -> p.message.resolveDeclaration(type) @@ -117,7 +128,7 @@ data class ProtoMessage( override fun validate() { super.validate() super.validate() - super.validate() + super.validate() super.validate() messages.forEach { it.validate() } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoDeclaration.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoStructureDeclaration.kt similarity index 87% rename from kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoDeclaration.kt rename to kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoStructureDeclaration.kt index 4c6c0ff4..3a3cf0c0 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoDeclaration.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/ProtoStructureDeclaration.kt @@ -2,6 +2,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration import com.squareup.kotlinpoet.ClassName import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoDeclParent import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoLanguageVersion import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.Options @@ -9,7 +10,7 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.Options /** * Base interface of both messages and enums */ -sealed interface ProtoDeclaration : ProtoBaseDeclaration { +sealed interface ProtoStructureDeclaration : ProtoBaseDeclaration { /** * The parent node of this declaration. @@ -39,7 +40,7 @@ sealed interface ProtoDeclaration : ProtoBaseDeclaration { override val className: ClassName get() { return when (val p = parent) { - is ProtoDeclParent.Message -> p.message.className.nestedClass(name) + is ProtoDeclParent.Message -> p.message.className.nestedClass(transformedKotlinName) is ProtoDeclParent.File -> super.className } } @@ -50,6 +51,12 @@ sealed interface ProtoDeclaration : ProtoBaseDeclaration { is ProtoDeclParent.File -> super.isNested } + override val codeNameResolver: CodeNameResolver + get() = when (val p = parent) { + is ProtoDeclParent.File -> super.codeNameResolver + is ProtoDeclParent.Message -> p.message.classNameResolver + } + val isProtoTopLevel: Boolean get() = when (parent) { is ProtoDeclParent.File -> true diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/enumeration/ProtoEnumField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/enumeration/ProtoEnumField.kt index 54d4b20e..1b3e9210 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/enumeration/ProtoEnumField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/enumeration/ProtoEnumField.kt @@ -1,8 +1,13 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.enumeration import com.squareup.kotlinpoet.ClassName +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile @@ -14,7 +19,7 @@ data class ProtoEnumField( override val number: Int, override val options: List, override val ctx: ParserRuleContext -) : ProtoField { +) : ProtoField, SourceCodeNamedNode { lateinit var enum: ProtoEnum override val parentOptionsHolder: ProtoOptionsHolder @@ -23,7 +28,15 @@ data class ProtoEnumField( override val file: ProtoFile get() = enum.file + override val project: ProtoProject + get() = file.project + + override val kotlinIdiomaticTextCase: TextCase = StandardTextCases.PASCAL_CASE + override val optionTarget: OptionTarget get() = OptionTarget.ENUM_ENTRY - val className: ClassName get() = enum.className.nestedClass(name) + val className: ClassName get() = enum.className.nestedClass(transformedKotlinName) + + override val codeNameResolver: CodeNameResolver + get() = enum } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt new file mode 100644 index 00000000..59336ef3 --- /dev/null +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt @@ -0,0 +1,18 @@ +package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message + +import dev.turingcomplete.textcaseconverter.StandardTextCases +import io.github.timortel.kmpgrpc.plugin.NamingStrategy +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.util.capitalize + +interface OneOfSealedClassNameHolder : SourceCodeNamedNode { + + val sealedClassRawName: String + get() = when (project.namingStrategy) { + NamingStrategy.PROTO_LITERAL -> transformedKotlinName.capitalize() + NamingStrategy.KOTLIN_IDIOMATIC -> kotlinIdiomaticTextCase.convertTo( + StandardTextCases.PASCAL_CASE, + transformedKotlinName + ) + } +} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoMessageProperty.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoMessageProperty.kt index 60782016..b36d6fa1 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoMessageProperty.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoMessageProperty.kt @@ -2,7 +2,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.mes import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.DeclarationResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildProperty -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildPropertyNameResolver +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile @@ -16,8 +16,8 @@ interface ProtoMessageProperty : ProtoChildProperty { val file: ProtoFile - override val resolvingParent: ProtoChildPropertyNameResolver - get() = message + override val codeNameResolver: CodeNameResolver + get() = message.fieldNameResolver val declarationResolver: DeclarationResolver } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoOneOf.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoOneOf.kt index dbb71b82..32a924fd 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoOneOf.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/ProtoOneOf.kt @@ -5,11 +5,11 @@ import com.squareup.kotlinpoet.TypeName import io.github.timortel.kmpgrpc.plugin.sourcegeneration.constants.Const import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.DeclarationResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoNode -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.util.capitalize import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildProperty -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildPropertyNameResolver +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoOneOfField @@ -17,7 +17,8 @@ data class ProtoOneOf( override val name: String, val fields: List, val options: List -) : ProtoMessageProperty, ProtoNode, ProtoChildPropertyNameResolver { +) : ProtoMessageProperty, ProtoNode, CodeNameResolver, OneOfSealedClassNameHolder { + companion object { private const val UNSET_CLASS_NAME = "NotSet" } @@ -26,20 +27,21 @@ data class ProtoOneOf( override val file: ProtoFile get() = message.file - val sealedClassName: ClassName get() = message.className.nestedClass(name.capitalize()) + val sealedClassName: ClassName get() = message.className.nestedClass(sealedClassRawName) val sealedClassNameNotSet: ClassName get() = sealedClassName.nestedClass(UNSET_CLASS_NAME) - override val desiredAttributeName: String = name + override val project: ProtoProject + get () = file.project override val propertyType: TypeName get() = sealedClassName - override val childProperties: List + override val consideredNodes: List get() = fields - override val reservedAttributeNames: Set - get() = Const.Message.OneOf.reservedAttributeNames + override val reservedNames: Set + get() = Const.Message.OneOf.reservedClassNames override val priority: Int get() = fields.minOf { it.number } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoExtensionField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoExtensionField.kt new file mode 100644 index 00000000..cce7ceee --- /dev/null +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoExtensionField.kt @@ -0,0 +1,22 @@ +package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field + +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoExtensionDefinition +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoMessageProperty + +interface ProtoExtensionField : ProtoMessageProperty { + + val parent: Parent + + override val codeNameResolver: CodeNameResolver + get() = when (val p = parent) { + is Parent.ExtensionDefinition -> p.ext + is Parent.Message -> p.message.fieldNameResolver + } + + sealed interface Parent { + data class Message(val message: ProtoMessage) : Parent + data class ExtensionDefinition(val ext: ProtoExtensionDefinition) : Parent + } +} diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt index 5bdef819..dce80ae3 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt @@ -7,6 +7,7 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.DeclarationResol import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoMessageProperty @@ -29,7 +30,12 @@ data class ProtoMapField( override val file: ProtoFile get() = message.file - override val desiredAttributeName: String = "${name}Map" + override val project: ProtoProject + get() = file.project + + + override val desiredCodeName: String + get() = if (transformedKotlinName.endsWith("Map")) transformedKotlinName else "${transformedKotlinName}Map" override val propertyType: TypeName get() = MAP.parameterizedBy(keyType.resolve(), valuesType.resolve()) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt index 40c6f176..27c215c5 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt @@ -10,10 +10,11 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoExtensionDe import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoLanguageVersion import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildProperty -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildPropertyNameResolver +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoMessageProperty +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoExtensionField.Parent import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.OptionTarget import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.Options @@ -29,9 +30,12 @@ class ProtoMessageField( override val options: List, private val fieldCardinality: FieldCardinality, override val ctx: ParserRuleContext -) : ProtoRegularField(), ProtoMessageProperty { +) : ProtoRegularField(), ProtoExtensionField { - lateinit var parent: Parent + override lateinit var parent: Parent + + override val project: ProtoProject + get() = file.project override val parentOptionsHolder: ProtoOptionsHolder? get() = when (val p = parent) { @@ -80,10 +84,10 @@ class ProtoMessageField( } } - override val desiredAttributeName: String + override val desiredCodeName: String get() = when (cardinality) { - is ProtoFieldCardinality.Singular -> name - ProtoFieldCardinality.Repeated -> "${name}List" + is ProtoFieldCardinality.Singular -> transformedKotlinName + ProtoFieldCardinality.Repeated -> if (transformedKotlinName.endsWith("List")) transformedKotlinName else "${transformedKotlinName}List" } override val propertyType: TypeName @@ -113,10 +117,11 @@ class ProtoMessageField( val isSetProperty: ExtraProperty get() = ExtraProperty( - desiredAttributeName = "is${name.capitalize()}Set", - resolvingParent = resolvingParent, + desiredCodeName = "is${transformedKotlinName.capitalize()}Set", + codeNameResolver = codeNameResolver, priority = number, - propertyType = BOOLEAN + propertyType = BOOLEAN, + project = project ) val childProperties: List @@ -196,18 +201,14 @@ class ProtoMessageField( } data class ExtraProperty( - override val desiredAttributeName: String, - override val resolvingParent: ProtoChildPropertyNameResolver, + override val desiredCodeName: String, + override val codeNameResolver: CodeNameResolver, override val priority: Int, - override val propertyType: TypeName + override val propertyType: TypeName, + override val project: ProtoProject ) : ProtoChildProperty { override val name: String - get() = desiredAttributeName - } - - sealed interface Parent { - data class Message(val message: ProtoMessage) : Parent - data class ExtensionDefinition(val ext: ProtoExtensionDefinition) : Parent + get() = desiredCodeName } /** diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoOneOfField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoOneOfField.kt index 013ce3ff..c8232a27 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoOneOfField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoOneOfField.kt @@ -2,14 +2,15 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.mes import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeName -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.util.capitalize -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoChildPropertyNameResolver -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.OneOfSealedClassNameHolder import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoOneOf +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.OptionTarget +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType import org.antlr.v4.runtime.ParserRuleContext data class ProtoOneOfField( @@ -18,7 +19,7 @@ data class ProtoOneOfField( override val number: Int, override val options: List, override val ctx: ParserRuleContext -) : ProtoRegularField() { +) : ProtoRegularField(), OneOfSealedClassNameHolder { lateinit var parent: ProtoOneOf @@ -27,14 +28,11 @@ data class ProtoOneOfField( override val file: ProtoFile get() = parent.file - override val desiredAttributeName: String - get() = name - - override val resolvingParent: ProtoChildPropertyNameResolver - get() = parent + override val project: ProtoProject + get() = file.project val sealedClassChildName: ClassName - get() = parent.sealedClassName.nestedClass(name.capitalize()) + get() = parent.sealedClassName.nestedClass(sealedClassRawName) override val propertyType: TypeName get() = sealedClassChildName @@ -43,6 +41,9 @@ data class ProtoOneOfField( override val optionTarget: OptionTarget get() = OptionTarget.FIELD(type = OptionTarget.FIELD.Type.OneOf) + override val codeNameResolver: CodeNameResolver + get() = parent + init { type.parent = ProtoType.Parent.OneOfField(this) } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt index a58dab7b..ad62a2b0 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt @@ -1,9 +1,10 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file import com.squareup.kotlinpoet.ClassName -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.* -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.OptionTarget @@ -26,7 +27,7 @@ data class ProtoFile( val imports: List, override val extensionDefinitions: List ) : FileBasedDeclarationResolver, ProtoOptionsHolder, ProtoVisibilityHolder, ProtoExtensionDefinitionHolder, - ProtoExtensionDefinitionFinder { + ProtoExtensionDefinitionFinder, SourceCodeNamedNode { lateinit var folder: ProtoFolder lateinit var protoPackage: ProtoPackage @@ -51,16 +52,6 @@ data class ProtoFile( override val candidates: List = messages.map { DeclarationResolver.Candidate.Message(it) } + enums.map { DeclarationResolver.Candidate.Enum(it) } - val importedFiles: List - get() = imports.map { import -> - project.rootFolder.resolveImport(import.path) - ?: throw CompilationException.UnresolvedImport( - "Unable to resolve import ${import.identifier}", - this, - import.ctx - ) - } - val javaPackage: String get() { return Options.Basic.javaPackage.get(this) ?: `package`.orEmpty() @@ -77,6 +68,24 @@ data class ProtoFile( override val parentOptionsHolder: ProtoOptionsHolder? = null + val topLevelDeclarations: List = messages + enums + services + extensionDefinitions.flatMap { it.fields } + + val declarationCodeNameResolver = object : CodeNameResolver { + override val reservedNames: Set = emptySet() + + override val consideredNodes: List = topLevelDeclarations + } + + override val kotlinIdiomaticTextCase: TextCase + get() = StandardTextCases.PASCAL_CASE + + override val name: String = fileName + + override val codeNameResolver: CodeNameResolver + get() = project.getCodeNameResolverForKotlinPackage(javaPackage) + + val dslFile = DslFile(this) + init { val parent = ProtoDeclParent.File(this) @@ -87,7 +96,7 @@ data class ProtoFile( extensionDefinitions.forEach { it.parent = ProtoExtensionDefinition.Parent.File(this) } } - override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoDeclaration? { + override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoStructureDeclaration? { return protoPackage.resolveDeclaration(type) } @@ -101,4 +110,19 @@ data class ProtoFile( services.forEach { it.validate() } imports.forEach { it.validate() } } + + class DslFile(val file: ProtoFile) : SourceCodeNamedNode { + override val project: ProtoProject + get() = file.project + + override val kotlinIdiomaticTextCase: TextCase = StandardTextCases.PASCAL_CASE + + override val name: String = file.name + override val desiredCodeName: String = "${name}Dsl" + + override val codeNameResolver: CodeNameResolver + get() = file.codeNameResolver + + val className get() = ClassName(file.javaPackage, codeName) + } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoRpc.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoRpc.kt index c3c77243..b0d15cab 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoRpc.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoRpc.kt @@ -1,24 +1,31 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.service +import dev.turingcomplete.textcaseconverter.StandardTextCases +import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.CodeNameResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.SourceCodeNamedNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.OptionTarget import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType import io.github.timortel.kmpgrpc.plugin.sourcegeneration.util.capitalize data class ProtoRpc( - val name: String, + override val name: String, val sendType: ProtoType.DefType, val returnType: ProtoType.DefType, val isSendingStream: Boolean, val isReceivingStream: Boolean, override val options: List -) : ProtoOptionsHolder { +) : ProtoOptionsHolder, SourceCodeNamedNode { lateinit var service: ProtoService override val file: ProtoFile get() = service.file + override val project: ProtoProject get() = file.project + val jvmMethodDescriptorName: String = "methodDescriptor${name.capitalize()}" override val optionTarget: OptionTarget = OptionTarget.METHOD @@ -33,6 +40,14 @@ data class ProtoRpc( else -> MethodType.UNARY } + override val kotlinIdiomaticTextCase: TextCase = StandardTextCases.SOFT_CAMEL_CASE + + override val codeNameResolver: CodeNameResolver + get() = service + + override val priority: Int + get() = 1 + init { sendType.parent = ProtoType.Parent.Rpc(this) returnType.parent = ProtoType.Parent.Rpc(this) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoService.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoService.kt index 33e0e565..c8729542 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoService.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/service/ProtoService.kt @@ -1,8 +1,6 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.service -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoNode -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.* import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoBaseDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.option.OptionTarget @@ -13,11 +11,18 @@ data class ProtoService( val rpcs: List, override val options: List, override val ctx: ParserRuleContext -) : ProtoBaseDeclaration, ProtoNode { +) : ProtoBaseDeclaration, ProtoNode, CodeNameResolver { override lateinit var file: ProtoFile - override val kotlinClassName: String = "${name}Stub" + override val desiredCodeName: String + get() = "${transformedKotlinName}Stub" + + override val consideredNodes: List + get() = rpcs + + override val reservedNames: Set + get() = emptySet() init { rpcs.forEach { it.service = this } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/structure/ProtoPackage.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/structure/ProtoPackage.kt index 151b36fa..ea6b981a 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/structure/ProtoPackage.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/structure/ProtoPackage.kt @@ -7,7 +7,7 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.type.ProtoType -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration data class ProtoPackage(val name: String, val packages: List, val files: List) : DeclarationResolver, ProtoNode, ProtoExtensionDefinitionFinder { @@ -34,7 +34,7 @@ data class ProtoPackage(val name: String, val packages: List, val packages.forEach { it.validate() } } - override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoDeclaration? { + override fun resolveDeclarationInParent(type: ProtoType.DefType): ProtoStructureDeclaration? { return when (val p = parent) { is Parent.Package -> p.`package`.resolveDeclaration(type) // Base package reached, no more resolution possible @@ -46,6 +46,13 @@ data class ProtoPackage(val name: String, val packages: List, val return packages.flatMap { it.findExtensionDefinitions() } + files.flatMap { it.findExtensionDefinitions() } } + /** + * Recursively finds all files that match the predicate + */ + fun findFiles(predicate: (ProtoFile) -> Boolean): List { + return files.filter(predicate) + packages.flatMap { it.findFiles(predicate) } + } + sealed interface Parent { data class Package(val `package`: ProtoPackage) : Parent data class Project(val project: ProtoProject) : Parent diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt index 1ab4cd4d..ced15ea3 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt @@ -8,7 +8,7 @@ import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoLanguageVer import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoNode import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOptionsHolder import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject -import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoDeclaration +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMapField @@ -252,7 +252,7 @@ sealed interface ProtoType : ProtoNode { return resolveDeclaration().className } - fun resolveDeclaration(): ProtoDeclaration { + fun resolveDeclaration(): ProtoStructureDeclaration { val decl = if (declaration.startsWith('.')) { val fullyQualifiedName = declaration.substring(1) diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/DeprecatedOptionGenerationTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/DeprecatedOptionGenerationTest.kt index 97bb7833..748091d1 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/DeprecatedOptionGenerationTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/DeprecatedOptionGenerationTest.kt @@ -3,6 +3,7 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.generation import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asTypeName +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kmpgrpc.plugin.sourcegeneration.SourceTarget import io.github.timortel.kotlin_multiplatform_grpc_plugin.createSingleFileProtoFolderFromResource @@ -23,7 +24,8 @@ class DeprecatedOptionGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) val deprecatedProperties = listOf( diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/NestInFileClassGenerationTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/NestInFileClassGenerationTest.kt index bb396ee6..e495b146 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/NestInFileClassGenerationTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/NestInFileClassGenerationTest.kt @@ -2,6 +2,7 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.generation import com.google.testing.junit.testparameterinjector.junit5.TestParameter import com.google.testing.junit.testparameterinjector.junit5.TestParameterInjectorTest +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kmpgrpc.plugin.sourcegeneration.SourceTarget import io.github.timortel.kotlin_multiplatform_grpc_plugin.createSingleFileProtoFolder @@ -30,7 +31,8 @@ class NestInFileClassGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.values.forEach { collection -> @@ -55,7 +57,8 @@ class NestInFileClassGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.entries.forEach { (target, collection) -> @@ -94,7 +97,8 @@ class NestInFileClassGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.entries.forEach { (target, collection) -> @@ -143,7 +147,8 @@ class NestInFileClassGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.values.forEach { collection -> @@ -172,7 +177,8 @@ class NestInFileClassGenerationTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.entries.forEach { (target, collection) -> diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/VisibilityTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/VisibilityTest.kt index 9c577755..7e76ab3b 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/VisibilityTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generation/VisibilityTest.kt @@ -3,6 +3,7 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.generation import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeSpec +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kotlin_multiplatform_grpc_plugin.createSingleFileProtoFolderFromResource import org.junit.jupiter.api.Assertions @@ -30,7 +31,8 @@ class VisibilityTest : BaseGenerationTest() { ) ), shouldGenerateTargetMap = targetMapAll, - internalVisibility = internalVisibility + internalVisibility = internalVisibility, + namingStrategy = NamingStrategy.PROTO_LITERAL ) fileMap.values.forEach { files -> diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/BaseModelTreeTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/BaseModelTreeTest.kt index 346eb441..104cba70 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/BaseModelTreeTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/BaseModelTreeTest.kt @@ -1,11 +1,18 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.modeltree +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoProject +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoMessage +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.enumeration.ProtoEnumField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.ProtoOneOf +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoOneOfField import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.service.ProtoRpc +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.service.ProtoService import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.structure.ProtoPackage import io.github.timortel.kotlin_multiplatform_grpc_plugin.createSingleFileProtoFolder import io.github.timortel.kotlin_multiplatform_grpc_plugin.validation.BaseValidationTest.ProtoVersion @@ -18,10 +25,19 @@ abstract class BaseModelTreeTest { val logger: Logger = spyk(LoggerFactory.getLogger("testlogger")) - protected fun buildProject(content: String, protoVersion: ProtoVersion): ProtoProject { + protected fun buildProject( + content: String, + protoVersion: ProtoVersion, + namingStrategy: NamingStrategy = NamingStrategy.PROTO_LITERAL + ): ProtoProject { val folder = createSingleFileProtoFolder(protoVersion.header, content) - return ProtoSourceGenerator.buildProtoProject(logger, listOf(folder), false) + return ProtoSourceGenerator.buildProtoProject( + logger = logger, + protoFolders = listOf(folder), + internalVisibility = false, + namingStrategy = namingStrategy + ) } protected fun ProtoProject.findMessage(name: String): ProtoMessage { @@ -29,17 +45,30 @@ abstract class BaseModelTreeTest { } protected fun ProtoPackage.findMessage(name: String): ProtoMessage? { - return files.firstNotNullOfOrNull { it.findMessage(name) } ?: packages.firstNotNullOfOrNull { it.findMessage(name) } + return find { it.findMessage(name) } + } + + protected fun ProtoProject.findEnum(name: String): ProtoEnum { + return rootPackage.find { file -> file.findEnum(name) } + ?: throw RuntimeException("Could not find enum with name $name") } protected fun ProtoFile.findMessage(name: String): ProtoMessage? { return messages.firstOrNull { it.name == name } ?: messages.firstNotNullOfOrNull { it.findMessage(name) } } + protected fun ProtoFile.findEnum(name: String): ProtoEnum? { + return enums.firstOrNull { it.name == name } ?: messages.firstNotNullOfOrNull { it.findEnum(name) } + } + protected fun ProtoMessage.findMessage(name: String): ProtoMessage? { return messages.firstOrNull { it.name == name } ?: messages.firstNotNullOfOrNull { it.findMessage(name) } } + protected fun ProtoMessage.findEnum(name: String): ProtoEnum? { + return enums.firstOrNull { it.name == name } ?: messages.firstNotNullOfOrNull { it.findEnum(name) } + } + protected fun ProtoMessage.findField(name: String): ProtoField { return fields.firstOrNull { it.name == name } ?: mapFields.firstOrNull { it.name == name } @@ -51,6 +80,43 @@ abstract class BaseModelTreeTest { return fields.firstOrNull { it.name == name } } + protected fun ProtoMessage.findOneOf(name: String): ProtoOneOf { + return oneOfs.firstOrNull { it.name == name } ?: throw RuntimeException("Could not find one of with name $name") + } + + protected fun ProtoProject.findExtensionField(name: String): ProtoMessageField { + return rootPackage.find { file -> + file + .findExtensionDefinitions() + .flatMap { it.fields } + .firstOrNull { it.name == name } + } ?: throw RuntimeException("Could not find extension field with name $name") + } + + protected fun ProtoProject.findService(name: String): ProtoService { + return rootPackage.find { file -> + file.services.firstOrNull { it.name == name } + } ?: throw RuntimeException("Could not find service with name $name") + } + + protected fun ProtoService.findRpc(name: String): ProtoRpc { + return rpcs.firstOrNull { it.name == name } ?: throw RuntimeException("Could not find rpc with name $name") + } + + protected fun ProtoOneOf.findCase(name: String): ProtoOneOfField { + return fields.firstOrNull { it.name == name } ?: throw RuntimeException("Could not find case with name $name") + } + + protected fun ProtoEnum.findEntry(name: String): ProtoEnumField { + return fields.firstOrNull { it.name == name } ?: throw RuntimeException("Could not find enum entry with name $name") + } + + private fun ProtoPackage.find(block: (ProtoFile) -> T?): T? { + return files.firstNotNullOfOrNull { block(it) } ?: packages.firstNotNullOfOrNull { + it.find(block) + } + } + protected inline fun Any.assertIsInstance(): T { return Assertions.assertInstanceOf(T::class.java, this) } diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/NamingStrategyTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/NamingStrategyTest.kt new file mode 100644 index 00000000..063f9c34 --- /dev/null +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/NamingStrategyTest.kt @@ -0,0 +1,280 @@ +package io.github.timortel.kotlin_multiplatform_grpc_plugin.modeltree + +import io.github.timortel.kmpgrpc.plugin.NamingStrategy +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMapField +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField +import io.github.timortel.kotlin_multiplatform_grpc_plugin.validation.BaseValidationTest.ProtoVersion +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class NamingStrategyTest : BaseModelTreeTest() { + + // --- MESSAGE & FIELD NAMING --- + + @Test + fun testKotlinIdiomaticNamingStrategy() { + val project = buildProject( + content = """ + message user_profile_data { + int32 user_id = 1; + string display_name = 2; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("user_profile_data") + Assertions.assertEquals("UserProfileData", msg.className.simpleName) + + val idField = msg.findField("user_id").assertIsInstance() + Assertions.assertEquals("userId", idField.codeName) + + val nameField = msg.findField("display_name").assertIsInstance() + Assertions.assertEquals("displayName", nameField.codeName) + } + + @Test + fun testProtoLiteralNamingStrategy() { + val project = buildProject( + content = """ + message message_a { + int32 field_1 = 1; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.PROTO_LITERAL + ) + + val msg = project.findMessage("message_a") + Assertions.assertEquals("message_a", msg.className.simpleName) + + val field = msg.findField("field_1").assertIsInstance() + Assertions.assertEquals("field_1", field.codeName) + } + + // --- REPEATED FIELDS & MAPS --- + + @Test + fun testCollectionNamingConventions() { + val project = buildProject( + content = """ + message collection_container { + repeated string tag_names = 1; + map attribute_map = 2; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("collection_container") + + // Repeated fields: fieldName + List + val repeatedField = msg.findField("tag_names").assertIsInstance() + Assertions.assertEquals("tagNamesList", repeatedField.codeName) + + // Maps: mapName (usually mapping to a Kotlin Map property) + val mapField = msg.findField("attribute_map").assertIsInstance() + Assertions.assertEquals("attributeMap", mapField.codeName) + } + + // --- PROTO2 EXTENSIONS --- + + @Test + fun testProto2ExtensionNaming() { + val project = buildProject( + content = """ + message BaseMessage { + extensions 100 to 200; + } + + extend BaseMessage { + optional string extension_field_name = 101; + repeated int32 extra_score = 102; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO2, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val scalarExt = project.findExtensionField("extension_field_name") + Assertions.assertEquals("extensionFieldName", scalarExt.codeName) + + val repeatedExt = project.findExtensionField("extra_score") + Assertions.assertEquals("extraScoreList", repeatedExt.codeName) + } + + // --- SERVICES & RPCS --- + + @Test + fun testServiceAndRpcNaming() { + val project = buildProject( + content = """ + service auth_manager { + rpc login_user (LoginRequest) returns (LoginResponse); + rpc GetStatus (Empty) returns (Status); + } + message LoginRequest {} + message LoginResponse {} + message Empty {} + message Status {} + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val service = project.findService("auth_manager") + Assertions.assertEquals("AuthManagerStub", service.className.simpleName) + + // rpc login_user -> loginUser + val loginMethod = service.findRpc("login_user") + Assertions.assertEquals("loginUser", loginMethod.transformedKotlinName) + + // rpc GetStatus -> getStatus (Pascal to lowerCamel) + val statusMethod = service.findRpc("GetStatus") + Assertions.assertEquals("getStatus", statusMethod.transformedKotlinName) + } + + // --- EDGE CASES & RESERVED KEYWORDS --- + + @Test + fun testReservedKeywordsAndEdgeCases() { + val project = buildProject( + content = """ + message keyword_holder { + string class = 1; + string val = 2; + string __internal_id__ = 3; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("keyword_holder") + + // KotlinPoet handles backticks; the model attribute name should be the clean string + val classField = msg.findField("class").assertIsInstance() + Assertions.assertEquals("class", classField.codeName) + + val valField = msg.findField("val").assertIsInstance() + Assertions.assertEquals("val", valField.codeName) + + // Clean up underscores + val internalField = msg.findField("__internal_id__").assertIsInstance() + Assertions.assertEquals("internalId", internalField.codeName) + } + + // --- ONEOFS --- + + @Test + fun testOneOfNamingStrategy() { + val project = buildProject( + content = """ + message identity_provider { + oneof authentication_method { + string email_address = 1; + int32 phone_number = 2; + string oauth_token = 3; + } + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("identity_provider") + + // 1. The property in the class representing the oneof + val oneOf = msg.findOneOf("authentication_method") + Assertions.assertEquals("authenticationMethod", oneOf.codeName) + + // 2. The Sealed Class/Interface name + Assertions.assertEquals("AuthenticationMethod", oneOf.sealedClassName.simpleName) + + // 3. The individual cases inside the OneOf + val emailCase = oneOf.findCase("email_address") + Assertions.assertEquals("EmailAddress", emailCase.sealedClassChildName.simpleName) + + val phoneCase = oneOf.findCase("phone_number") + Assertions.assertEquals("PhoneNumber", phoneCase.sealedClassChildName.simpleName) + } + + // --- ENUMS --- + + @Test + fun testEnumNamingStrategy() { + val project = buildProject( + content = """ + enum user_status { + USER_STATUS_UNSPECIFIED = 0; + USER_STATUS_ACTIVE = 1; + user_status_suspended = 2; + DELETED = 3; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val protoEnum = project.findEnum("user_status") + Assertions.assertEquals("UserStatus", protoEnum.className.simpleName) + + Assertions.assertEquals("UserStatusUnspecified", protoEnum.findEntry("USER_STATUS_UNSPECIFIED").className.simpleName) + Assertions.assertEquals("UserStatusSuspended", protoEnum.findEntry("user_status_suspended").className.simpleName) + Assertions.assertEquals("Deleted", protoEnum.findEntry("DELETED").className.simpleName) + } + + // --- MIXED INPUTS & NUMBERS --- + + @Test + fun testMixedNamingInputsAndNumbers() { + val project = buildProject( + content = """ + message mixed_input { + string AlreadyCamelCase = 1; + string snake_case_field = 2; + string SCREAMING_SNAKE = 3; + string simple = 4; + string address_line_1 = 5; + string DNS_Record = 6; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("mixed_input") + + // PascalCase to lowerCamelCase + Assertions.assertEquals( + "alreadyCamelCase", + msg.findField("AlreadyCamelCase").assertIsInstance().codeName + ) + + // Standard snake_case + Assertions.assertEquals( + "snakeCaseField", + msg.findField("snake_case_field").assertIsInstance().codeName + ) + + // Screaming snake to lowerCamelCase + Assertions.assertEquals( + "screamingSnake", + msg.findField("SCREAMING_SNAKE").assertIsInstance().codeName + ) + + // Numbers handling + Assertions.assertEquals( + "addressLine1", + msg.findField("address_line_1").assertIsInstance().codeName + ) + + // Acronyms/Mixed case with underscores + Assertions.assertEquals( + "dnsRecord", + msg.findField("DNS_Record").assertIsInstance().codeName + ) + } +} diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/validation/BaseValidationTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/validation/BaseValidationTest.kt index 72fbf8df..aac12250 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/validation/BaseValidationTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/validation/BaseValidationTest.kt @@ -1,5 +1,6 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.validation +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.InputFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kmpgrpc.plugin.sourcegeneration.SourceTarget @@ -27,7 +28,8 @@ abstract class BaseValidationTest { SourceTarget.Js to true, SourceTarget.Native to true ), - internalVisibility = false + internalVisibility = false, + namingStrategy = NamingStrategy.PROTO_LITERAL ) } From 19c0c1c8c6aad8a1636e61b5390fb0df19322c49 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:24:14 +0100 Subject: [PATCH 02/14] Change conflict resolution strategy for files to numbers instead of underscores. Add tests --- .../model/CodeNameResolver.kt | 28 +++++++- .../sourcegeneration/model/ProtoProject.kt | 3 + .../sourcegeneration/model/file/ProtoFile.kt | 3 + .../modeltree/DeclarationClashTest.kt | 69 +++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DeclarationClashTest.kt diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt index a82e472c..d8972bc1 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/CodeNameResolver.kt @@ -9,7 +9,7 @@ interface CodeNameResolver { val consideredNodes: List - fun resolveNameConflict(name: String): String = "${name}_" + val conflictResolutionStrategy: ConflictResolutionStrategy get() = ConflictResolutionStrategy.appendUnderscore fun resolveCodeName(field: SourceCodeNamedNode): String { val reservedNames = reservedNames.toMutableSet() @@ -20,8 +20,10 @@ interface CodeNameResolver { .forEach { currentNode -> var attributeName = currentNode.desiredCodeName + var conflictCount = 1 while (attributeName in reservedNames) { - attributeName = resolveNameConflict(attributeName) + attributeName = conflictResolutionStrategy.resolveNameConflict(currentNode.desiredCodeName, attributeName, conflictCount) + conflictCount++ } if (currentNode == field) return attributeName @@ -32,4 +34,26 @@ interface CodeNameResolver { throw IllegalArgumentException("field=$field not child of resolver=$this. Known children=$consideredNodes.") } + + interface ConflictResolutionStrategy { + fun resolveNameConflict(originalName: String, currentName: String, conflictCount: Int): String = "${currentName}_" + + companion object { + val appendUnderscore: ConflictResolutionStrategy = object : ConflictResolutionStrategy { + override fun resolveNameConflict( + originalName: String, + currentName: String, + conflictCount: Int + ): String = "${currentName}_" + } + + val appendNumber: ConflictResolutionStrategy = object : ConflictResolutionStrategy { + override fun resolveNameConflict( + originalName: String, + currentName: String, + conflictCount: Int + ): String = "${currentName}$conflictCount" + } + } + } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt index b19d6328..b6b0dde1 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/ProtoProject.kt @@ -61,6 +61,9 @@ data class ProtoProject( return object : CodeNameResolver { override val reservedNames: Set = emptySet() override val consideredNodes: List = consideredNodes + + override val conflictResolutionStrategy: CodeNameResolver.ConflictResolutionStrategy + get() = CodeNameResolver.ConflictResolutionStrategy.appendNumber } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt index ad62a2b0..42dc7fec 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt @@ -74,6 +74,9 @@ data class ProtoFile( override val reservedNames: Set = emptySet() override val consideredNodes: List = topLevelDeclarations + + override val conflictResolutionStrategy: CodeNameResolver.ConflictResolutionStrategy + get() = CodeNameResolver.ConflictResolutionStrategy.appendNumber } override val kotlinIdiomaticTextCase: TextCase diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DeclarationClashTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DeclarationClashTest.kt new file mode 100644 index 00000000..60b7dd80 --- /dev/null +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DeclarationClashTest.kt @@ -0,0 +1,69 @@ +package io.github.timortel.kotlin_multiplatform_grpc_plugin.modeltree + +import io.github.timortel.kmpgrpc.plugin.NamingStrategy +import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField +import io.github.timortel.kotlin_multiplatform_grpc_plugin.validation.BaseValidationTest.ProtoVersion +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class DeclarationClashTest : BaseModelTreeTest() { + + @Test + fun testNameCollisionResolution() { + val project = buildProject( + content = """ + message collision_test { + // These both map to 'myField' + string my_field = 1; + string myField = 2; + + // This matches the class name 'CollisionTest' after transformation + string CollisionTest = 3; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + val msg = project.findMessage("collision_test") + + // The first one gets the preferred name + val field1 = msg.findField("my_field").assertIsInstance() + Assertions.assertEquals("myField", field1.codeName) + + // The second one must be disambiguated + val field2 = msg.findField("myField").assertIsInstance() + Assertions.assertTrue(field2.codeName.startsWith("myField_")) + + // The field matching the class name must be disambiguated + // to avoid constructor/class shadowing + val field3 = msg.findField("CollisionTest").assertIsInstance() + Assertions.assertNotEquals("CollisionTest", field3.codeName) + } + + @Test + fun testClassNameCollisionResolution() { + val project = buildProject( + content = """ + // Peer Collision: Both map to 'UserRequest' + message user_request { + string id = 1; + } + message UserRequest { + string email = 1; + } + """.trimIndent(), + protoVersion = ProtoVersion.PROTO3, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) + + // Test Peer Collision Resolution + // The first message encountered should get the "clean" name. + val firstMsg = project.findMessage("user_request") + Assertions.assertEquals("UserRequest", firstMsg.className.simpleName) + + // The second message must be disambiguated (usually by appending a suffix). + val secondMsg = project.findMessage("UserRequest") + Assertions.assertEquals("UserRequest1", secondMsg.className.simpleName) + } +} From d7411fdbc2b78bf95582bd1cf19e4c22d1598900 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:51:22 +0100 Subject: [PATCH 03/14] Add LEGACY naming strategy. Fix tests. --- kmp-grpc-internal-test/build.gradle.kts | 3 ++ .../test/defaults.kt | 32 +++++++++---------- .../timortel/kmpgrpc/plugin/NamingStrategy.kt | 4 +++ .../model/SourceCodeNamedNode.kt | 2 +- .../message/OneOfSealedClassNameHolder.kt | 2 +- .../message/field/ProtoMapField.kt | 6 +++- .../message/field/ProtoMessageField.kt | 8 +++-- readme.md | 12 +++++++ 8 files changed, 48 insertions(+), 21 deletions(-) diff --git a/kmp-grpc-internal-test/build.gradle.kts b/kmp-grpc-internal-test/build.gradle.kts index 4dc58cd9..626b6239 100644 --- a/kmp-grpc-internal-test/build.gradle.kts +++ b/kmp-grpc-internal-test/build.gradle.kts @@ -1,3 +1,4 @@ +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent @@ -114,6 +115,8 @@ kmpGrpc { includeWellKnownTypes = true + namingStrategy = NamingStrategy.LEGACY + protoSourceFolders = project.files( "src/commonMain/proto/general", "src/commonMain/proto/unknownfield", diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt index 71fe0d36..ff3bb48f 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt @@ -77,13 +77,13 @@ fun createMessageWithAllExtensions() = ExtensionsTest.MessageWithEveryExtension( set(ExtensionsTest.field7, SimpleEnum.ONE) set(ExtensionsTest.field8, simpleMessage { field1 = "Foo" }) - set(ExtensionsTest.field9, listOf("Foo", "Bar", "Baz")) - set(ExtensionsTest.field10, listOf(true, false, true, true)) - set(ExtensionsTest.field11, listOf(1, 2, 3, 4, -12, 1341)) - set(ExtensionsTest.field12, listOf(12L, 23424L, 10312313L, -123131L)) - set(ExtensionsTest.field13, listOf(-1f, 2f, 2.5f, -0.5f)) - set(ExtensionsTest.field14, listOf(-0.5, 15.0)) - set(ExtensionsTest.field15, listOf(SimpleEnum.ZERO, SimpleEnum.ZERO, SimpleEnum.ONE, SimpleEnum.TWO)) + set(ExtensionsTest.field9List, listOf("Foo", "Bar", "Baz")) + set(ExtensionsTest.field10List, listOf(true, false, true, true)) + set(ExtensionsTest.field11List, listOf(1, 2, 3, 4, -12, 1341)) + set(ExtensionsTest.field12List, listOf(12L, 23424L, 10312313L, -123131L)) + set(ExtensionsTest.field13List, listOf(-1f, 2f, 2.5f, -0.5f)) + set(ExtensionsTest.field14List, listOf(-0.5, 15.0)) + set(ExtensionsTest.field15List, listOf(SimpleEnum.ZERO, SimpleEnum.ZERO, SimpleEnum.ONE, SimpleEnum.TWO)) set(ExtensionsTest.field19, 12u) set(ExtensionsTest.field20, 14uL) @@ -95,16 +95,16 @@ fun createMessageWithAllExtensions() = ExtensionsTest.MessageWithEveryExtension( set(ExtensionsTest.field26, -1353532131L) set(ExtensionsTest.field27, byteArrayOf(0, -13, 127)) - set(ExtensionsTest.field28, listOf(0u, 134u, 35311u)) - set(ExtensionsTest.field29, listOf(0uL, 134uL, 353111345134uL)) - set(ExtensionsTest.field30, listOf(-134, -145129, 34521431)) - set(ExtensionsTest.field31, listOf(-1L, 141341413413L, -134134314131L)) - set(ExtensionsTest.field32, listOf(0u, 14234u, 1413413413u)) - set(ExtensionsTest.field33, listOf(0uL, 134uL, 353111345134uL)) - set(ExtensionsTest.field34, listOf(-14, 0, 1241522)) - set(ExtensionsTest.field35, listOf(-154L, 0L, 4514124121L)) + set(ExtensionsTest.field28List, listOf(0u, 134u, 35311u)) + set(ExtensionsTest.field29List, listOf(0uL, 134uL, 353111345134uL)) + set(ExtensionsTest.field30List, listOf(-134, -145129, 34521431)) + set(ExtensionsTest.field31List, listOf(-1L, 141341413413L, -134134314131L)) + set(ExtensionsTest.field32List, listOf(0u, 14234u, 1413413413u)) + set(ExtensionsTest.field33List, listOf(0uL, 134uL, 353111345134uL)) + set(ExtensionsTest.field34List, listOf(-14, 0, 1241522)) + set(ExtensionsTest.field35List, listOf(-154L, 0L, 4514124121L)) set( - ExtensionsTest.field36, listOf( + ExtensionsTest.field36List, listOf( byteArrayOf(0, -127, 127), byteArrayOf(-123, 1, 2), byteArrayOf(3, 3, -6) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt index bae0720e..b085eae3 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/NamingStrategy.kt @@ -1,6 +1,10 @@ package io.github.timortel.kmpgrpc.plugin enum class NamingStrategy { + /** + * Keeps names as defined in .proto file. Additionally, applies "Map" and "List" suffixes to variable names if applicable. + */ + LEGACY, /** * Keeps names exactly as defined in the .proto file. * Example: message_name -> message_name, field_name -> field_name diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt index 6aaa41fb..546e37b8 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/SourceCodeNamedNode.kt @@ -23,7 +23,7 @@ interface SourceCodeNamedNode { */ val transformedKotlinName: String get() = when (project.namingStrategy) { - NamingStrategy.PROTO_LITERAL -> name + NamingStrategy.PROTO_LITERAL, NamingStrategy.LEGACY -> name NamingStrategy.KOTLIN_IDIOMATIC -> { // Ensure the input is treated as one cohesive "word" set before converting val normalizedName = name.trim('_') // Protobuf allows __internal__ names diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt index 59336ef3..4533fe90 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/OneOfSealedClassNameHolder.kt @@ -9,7 +9,7 @@ interface OneOfSealedClassNameHolder : SourceCodeNamedNode { val sealedClassRawName: String get() = when (project.namingStrategy) { - NamingStrategy.PROTO_LITERAL -> transformedKotlinName.capitalize() + NamingStrategy.PROTO_LITERAL, NamingStrategy.LEGACY -> transformedKotlinName.capitalize() NamingStrategy.KOTLIN_IDIOMATIC -> kotlinIdiomaticTextCase.convertTo( StandardTextCases.PASCAL_CASE, transformedKotlinName diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt index dce80ae3..7e95db90 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt @@ -3,6 +3,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.mes import com.squareup.kotlinpoet.MAP import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.TypeName +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.DeclarationResolver import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file.ProtoFile import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.ProtoOption @@ -35,7 +36,10 @@ data class ProtoMapField( override val desiredCodeName: String - get() = if (transformedKotlinName.endsWith("Map")) transformedKotlinName else "${transformedKotlinName}Map" + get() = when (project.namingStrategy) { + NamingStrategy.PROTO_LITERAL -> transformedKotlinName + NamingStrategy.KOTLIN_IDIOMATIC, NamingStrategy.LEGACY -> if (transformedKotlinName.endsWith("Map")) transformedKotlinName else "${transformedKotlinName}Map" + } override val propertyType: TypeName get() = MAP.parameterizedBy(keyType.resolve(), valuesType.resolve()) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt index 27c215c5..774b983d 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt @@ -3,6 +3,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.mes import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.MemberName.Companion.member import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.CompilationException import io.github.timortel.kmpgrpc.plugin.sourcegeneration.constants.Const import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.DeclarationResolver @@ -87,7 +88,10 @@ class ProtoMessageField( override val desiredCodeName: String get() = when (cardinality) { is ProtoFieldCardinality.Singular -> transformedKotlinName - ProtoFieldCardinality.Repeated -> if (transformedKotlinName.endsWith("List")) transformedKotlinName else "${transformedKotlinName}List" + ProtoFieldCardinality.Repeated -> when (project.namingStrategy) { + NamingStrategy.PROTO_LITERAL -> transformedKotlinName + NamingStrategy.KOTLIN_IDIOMATIC, NamingStrategy.LEGACY -> if (transformedKotlinName.endsWith("List")) transformedKotlinName else "${transformedKotlinName}List" + } } override val propertyType: TypeName @@ -147,7 +151,7 @@ class ProtoMessageField( is Parent.Message -> p.message.className } - return parentClassName.member(name) + return parentClassName.member(codeName) } override val optionTarget: OptionTarget diff --git a/readme.md b/readme.md index b13b8b84..94e5e0ae 100644 --- a/readme.md +++ b/readme.md @@ -432,6 +432,18 @@ kmpGrpc { // Optional: if all generated source files should have 'internal' visibility. internalVisibility = true + + /* + * Defines the naming convention for generated classes and properties. + * * - [NamingStrategy.KOTLIN_IDIOMATIC] (Default): Transforms names to match Kotlin + * conventions (e.g., snake_case fields become camelCase, messages become PascalCase). + * Repeated fields are automatically suffixed with "List". + * * - [NamingStrategy.PROTO_LITERAL]: Keeps names exactly as they are defined + * in the .proto source files. + * * - [NamingStrategy.LEGACY]: Matches the behavior of major version 1 of this library. Keeps the + * original .proto names but appends "List" and "Map" suffixes where applicable. + */ + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC // Specify the folders where your proto files are located, you can list multiple. protoSourceFolders = project.files("") From 49782b049e0861634d68854da8627c6932b633b2 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:57:07 +0100 Subject: [PATCH 04/14] Fix behavior of LEGACY naming strategy. --- .../model/declaration/message/field/ProtoMapField.kt | 3 ++- .../declaration/message/field/ProtoMessageField.kt | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt index 7e95db90..2c6bf36f 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMapField.kt @@ -38,7 +38,8 @@ data class ProtoMapField( override val desiredCodeName: String get() = when (project.namingStrategy) { NamingStrategy.PROTO_LITERAL -> transformedKotlinName - NamingStrategy.KOTLIN_IDIOMATIC, NamingStrategy.LEGACY -> if (transformedKotlinName.endsWith("Map")) transformedKotlinName else "${transformedKotlinName}Map" + NamingStrategy.LEGACY -> "${transformedKotlinName}Map" + NamingStrategy.KOTLIN_IDIOMATIC -> if (transformedKotlinName.endsWith("Map")) transformedKotlinName else "${transformedKotlinName}Map" } override val propertyType: TypeName diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt index 774b983d..da8b0c1b 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/declaration/message/field/ProtoMessageField.kt @@ -81,7 +81,9 @@ class ProtoMessageField( ) FieldCardinality.REPEATED -> ProtoFieldCardinality.Repeated - FieldCardinality.SINGULAR_OPTIONAL, FieldCardinality.SINGULAR_REQUIRED -> throw IllegalArgumentException("field cardinality $fieldCardinality is illegal for edition versions.") + FieldCardinality.SINGULAR_OPTIONAL, FieldCardinality.SINGULAR_REQUIRED -> throw IllegalArgumentException( + "field cardinality $fieldCardinality is illegal for edition versions." + ) } } @@ -90,7 +92,8 @@ class ProtoMessageField( is ProtoFieldCardinality.Singular -> transformedKotlinName ProtoFieldCardinality.Repeated -> when (project.namingStrategy) { NamingStrategy.PROTO_LITERAL -> transformedKotlinName - NamingStrategy.KOTLIN_IDIOMATIC, NamingStrategy.LEGACY -> if (transformedKotlinName.endsWith("List")) transformedKotlinName else "${transformedKotlinName}List" + NamingStrategy.LEGACY -> "${transformedKotlinName}List" + NamingStrategy.KOTLIN_IDIOMATIC -> if (transformedKotlinName.endsWith("List")) transformedKotlinName else "${transformedKotlinName}List" } } @@ -110,7 +113,8 @@ class ProtoMessageField( */ val needsIsSetProperty: Boolean get() { - val isSingularMessage = type is ProtoType.DefType && type.isMessage && cardinality != ProtoFieldCardinality.Repeated + val isSingularMessage = + type is ProtoType.DefType && type.isMessage && cardinality != ProtoFieldCardinality.Repeated return when (file.languageVersion) { ProtoLanguageVersion.PROTO2 -> cardinality is ProtoFieldCardinality.Singular From 9aadce9e8044e992b6a5c67078190f42ba83d5d8 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:33:10 +0100 Subject: [PATCH 05/14] Fix source generation. Fix tests. includeWellKnownTypes may only be used with KOTLIN_IDIOMATIC naming strategy. --- .../plugin/GenerateKmpGrpcSourcesTask.kt | 8 +++++++ .../kmpgrpc/plugin/KmpGrpcExtension.kt | 16 ++++++++++++++ .../enumeration/ProtoEnumerationWriter.kt | 2 +- .../sourcegeneration/model/file/ProtoFile.kt | 21 +++++++++++-------- .../sourcegeneration/model/type/ProtoType.kt | 2 +- .../io/github/timortel/kmpgrpc/wkt/ext/Any.kt | 10 ++++----- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt index 6bfe36f4..0f60e06b 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/GenerateKmpGrpcSourcesTask.kt @@ -3,6 +3,7 @@ package io.github.timortel.kmpgrpc.plugin import io.github.timortel.kmpgrpc.plugin.sourcegeneration.ProtoSourceGenerator import io.github.timortel.kmpgrpc.plugin.sourcegeneration.SystemInputFile import org.gradle.api.DefaultTask +import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty @@ -84,6 +85,13 @@ abstract class GenerateKmpGrpcSourcesTask : DefaultTask() { @TaskAction fun generateSources() { + if (namingStrategy.get() != NamingStrategy.KOTLIN_IDIOMATIC && includeWellKnownTypes.get()) { + throw GradleException( + "Invalid Configuration: 'includeWellKnownTypes' can only be set to true " + + "if 'namingStrategy' is set to KOTLIN_IDIOMATIC." + ) + } + val tsm = targetSourcesMap.get() val shouldGenerateTargetMap = KmpGrpcExtension.targets.associateWith { target -> diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt index 58e5cf14..978931a0 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/KmpGrpcExtension.kt @@ -31,6 +31,8 @@ open class KmpGrpcExtension @Inject constructor(objects: ObjectFactory) { /** * Instructs the plugin to download and include the well known proto-types: https://protobuf.dev/reference/protobuf/google.protobuf/ + * + * Can only be used in combination with `namingStrategy` `KOTLIN_IDIOMATIC`. */ val includeWellKnownTypes: Property = objects .property(Boolean::class.java) @@ -43,6 +45,20 @@ open class KmpGrpcExtension @Inject constructor(objects: ObjectFactory) { .property(Boolean::class.java) .convention(false) + /** + * Configures how Protobuf messages and fields are mapped to Kotlin identifiers. + * + * This property allows you to choose between standard Protobuf naming, idiomatic + * Kotlin style, or the legacy behavior. It affects the generated class names, + * property names, and method names. + * + * Supported strategies: + * - [NamingStrategy.KOTLIN_IDIOMATIC]: (Default) Transforms names to Kotlin conventions + * (e.g., `user_id` -> `userId`, `User_Message` -> `UserMessage`). + * - [NamingStrategy.PROTO_LITERAL]: Preserves the exact names defined in the .proto file. + * - [NamingStrategy.LEGACY]: Preserves .proto names but appends "List" or "Map" + * suffixes to collection types (Behavior from v1.x). + */ val namingStrategy: Property = objects .property(NamingStrategy::class.java) .convention(NamingStrategy.KOTLIN_IDIOMATIC) diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/enumeration/ProtoEnumerationWriter.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/enumeration/ProtoEnumerationWriter.kt index 51b43ed9..a59a563d 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/enumeration/ProtoEnumerationWriter.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/enumeration/ProtoEnumerationWriter.kt @@ -128,7 +128,7 @@ abstract class ProtoEnumerationWriter(val isActual: Boolean) { .beginControlFlow("when(num)") .apply { protoEnum.fields.forEach { field -> - add("%L -> %N\n", field.number, field.name) + add("%L -> %N\n", field.number, field.codeName) } if (orNullVersion) { add("else -> null\n") diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt index 42dc7fec..b86027cd 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/file/ProtoFile.kt @@ -3,6 +3,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.file import com.squareup.kotlinpoet.ClassName import dev.turingcomplete.textcaseconverter.StandardTextCases import dev.turingcomplete.textcaseconverter.TextCase +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.* import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoStructureDeclaration import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.ProtoEnum @@ -57,12 +58,16 @@ data class ProtoFile( return Options.Basic.javaPackage.get(this) ?: `package`.orEmpty() } - val javaFileName: String - get() { - return Options.Basic.javaOuterClassName.get(this) ?: fileNameWithoutExtension.snakeCaseToCamelCase() - } + override val name: String get() = Options.Basic.javaOuterClassName.get(this) ?: when (project.namingStrategy) { + NamingStrategy.LEGACY -> fileNameWithoutExtension.snakeCaseToCamelCase() + NamingStrategy.PROTO_LITERAL, NamingStrategy.KOTLIN_IDIOMATIC -> fileNameWithoutExtension + } - val className: ClassName get() = ClassName(javaPackage, javaFileName) + override val codeName: String by lazy { + super.codeName + } + + val className: ClassName get() = ClassName(javaPackage, codeName) override val optionTarget: OptionTarget = OptionTarget.FILE @@ -82,8 +87,6 @@ data class ProtoFile( override val kotlinIdiomaticTextCase: TextCase get() = StandardTextCases.PASCAL_CASE - override val name: String = fileName - override val codeNameResolver: CodeNameResolver get() = project.getCodeNameResolverForKotlinPackage(javaPackage) @@ -120,8 +123,8 @@ data class ProtoFile( override val kotlinIdiomaticTextCase: TextCase = StandardTextCases.PASCAL_CASE - override val name: String = file.name - override val desiredCodeName: String = "${name}Dsl" + override val name: String get() = file.name + override val desiredCodeName: String get() = "${name}Dsl" override val codeNameResolver: CodeNameResolver get() = file.codeNameResolver diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt index ced15ea3..cf5bec78 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/model/type/ProtoType.kt @@ -236,7 +236,7 @@ sealed interface ProtoType : ProtoNode { ?: throw CompilationException.UnresolvedReference("Could not find enum entry with name $defaultValueAsString", file, ctx) } - CodeBlock.of("%T.%N", decl.className, defaultValue.name) + CodeBlock.of("%T.%N", decl.className, defaultValue.codeName) } is ProtoMessage -> { diff --git a/kmp-grpc-wellknown-ext/src/commonMain/kotlin/io/github/timortel/kmpgrpc/wkt/ext/Any.kt b/kmp-grpc-wellknown-ext/src/commonMain/kotlin/io/github/timortel/kmpgrpc/wkt/ext/Any.kt index b32b7a43..83c68ea4 100644 --- a/kmp-grpc-wellknown-ext/src/commonMain/kotlin/io/github/timortel/kmpgrpc/wkt/ext/Any.kt +++ b/kmp-grpc-wellknown-ext/src/commonMain/kotlin/io/github/timortel/kmpgrpc/wkt/ext/Any.kt @@ -13,7 +13,7 @@ private const val DEFAULT_TYPE_URL_PREFIX = "type.googleapis.com" * @return a new [Any] message with the given [message] as content. */ public fun Any.Companion.wrap(message: T, typeUrlPrefix: String = DEFAULT_TYPE_URL_PREFIX): Any = any { - type_url = getTypeUrl(message.fullName, typeUrlPrefix) + typeUrl = getTypeUrl(message.fullName, typeUrlPrefix) value = message.serialize() } @@ -34,7 +34,7 @@ public fun > Any.unwrap(deserializer: * @return if the type held by this object matches [T] */ public fun Any.isType(message: MessageCompanion, typeUrlPrefix: String): Boolean { - return type_url == getTypeUrl(message.fullName, typeUrlPrefix) + return typeUrl == getTypeUrl(message.fullName, typeUrlPrefix) } /** @@ -44,7 +44,7 @@ public fun Any.isType(message: MessageCompanion, typeUrlPrefix: * @return if the type held by this object matches [T] */ public fun Any.isType(message: MessageCompanion): Boolean { - return type_url.substringAfterLast('/') == message.fullName + return typeUrl.substringAfterLast('/') == message.fullName } /** @@ -53,7 +53,7 @@ public fun Any.isType(message: MessageCompanion): Boolean { * @return if the type held by this object matches the type of [message] */ public fun Any.isSameTypeAs(message: Message, typeUrlPrefix: String): Boolean { - return type_url == getTypeUrl(message.fullName, typeUrlPrefix) + return typeUrl == getTypeUrl(message.fullName, typeUrlPrefix) } /** @@ -62,7 +62,7 @@ public fun Any.isSameTypeAs(message: Message, typeUrlPrefix: String): Boolean { * @return if the type held by this object matches the type of [message] */ public fun Any.isSameTypeAs(message: Message): Boolean { - return type_url.substringAfterLast('/') == message.fullName + return typeUrl.substringAfterLast('/') == message.fullName } private fun getTypeUrl(fullName: String, typeUrlPrefix: String): String = From de2e8bdf7cd831092e5022adb65e142be10f95e2 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:51:05 +0100 Subject: [PATCH 06/14] Use KOTLIN_IDIOMATIC naming strategy in tests --- kmp-grpc-internal-test/build.gradle.kts | 2 -- .../test/defaults.kt | 10 +++++----- .../test/model/DSLBuilderTest.kt | 14 ++++++------- ...OpenClosedEnumMapFieldSerializationTest.kt | 12 +++++------ .../OpenClosedEnumOneOfSerializationTest.kt | 8 ++++---- ...losedEnumRepeatedFieldSerializationTest.kt | 20 +++++++++---------- ...nClosedEnumScalarFieldSerializationTest.kt | 10 +++++----- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/kmp-grpc-internal-test/build.gradle.kts b/kmp-grpc-internal-test/build.gradle.kts index 626b6239..4ae9b56f 100644 --- a/kmp-grpc-internal-test/build.gradle.kts +++ b/kmp-grpc-internal-test/build.gradle.kts @@ -115,8 +115,6 @@ kmpGrpc { includeWellKnownTypes = true - namingStrategy = NamingStrategy.LEGACY - protoSourceFolders = project.files( "src/commonMain/proto/general", "src/commonMain/proto/unknownfield", diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt index ff3bb48f..c8a67a48 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt @@ -30,7 +30,7 @@ fun createMessageWithAllTypes() = messageWithEverything { field4 = 25L field5 = 3f field6 = 7.0 - field7 = SimpleEnum.ONE + field7 = SimpleEnum.One field8 = simpleMessage { field1 = "Foo" } field9List += listOf("Foo", "Bar", "Baz") @@ -39,11 +39,11 @@ fun createMessageWithAllTypes() = messageWithEverything { field12List += listOf(12L, 23424L, 10312313L, -123131L) field13List += listOf(-1f, 2f, 2.5f, -0.5f) field14List += listOf(-0.5, 15.0) - field15List += listOf(SimpleEnum.ZERO, SimpleEnum.ZERO, SimpleEnum.ONE, SimpleEnum.TWO) + field15List += listOf(SimpleEnum.Zero, SimpleEnum.Zero, SimpleEnum.One, SimpleEnum.Two) field16Map += mapOf("foo" to 1, "bar" to -13, "baz" to 112) field17Map += mapOf(1 to simpleMessage { field1 = "Foo" }, 13 to simpleMessage { field1 = "Baz" }) - field18Map += mapOf(-15 to SimpleEnum.ONE, 23 to SimpleEnum.TWO) + field18Map += mapOf(-15 to SimpleEnum.One, 23 to SimpleEnum.Two) field19 = 12u field20 = 14uL @@ -74,7 +74,7 @@ fun createMessageWithAllExtensions() = ExtensionsTest.MessageWithEveryExtension( set(ExtensionsTest.field4, 25L) set(ExtensionsTest.field5, 3f) set(ExtensionsTest.field6, 7.0) - set(ExtensionsTest.field7, SimpleEnum.ONE) + set(ExtensionsTest.field7, SimpleEnum.One) set(ExtensionsTest.field8, simpleMessage { field1 = "Foo" }) set(ExtensionsTest.field9List, listOf("Foo", "Bar", "Baz")) @@ -83,7 +83,7 @@ fun createMessageWithAllExtensions() = ExtensionsTest.MessageWithEveryExtension( set(ExtensionsTest.field12List, listOf(12L, 23424L, 10312313L, -123131L)) set(ExtensionsTest.field13List, listOf(-1f, 2f, 2.5f, -0.5f)) set(ExtensionsTest.field14List, listOf(-0.5, 15.0)) - set(ExtensionsTest.field15List, listOf(SimpleEnum.ZERO, SimpleEnum.ZERO, SimpleEnum.ONE, SimpleEnum.TWO)) + set(ExtensionsTest.field15List, listOf(SimpleEnum.Zero, SimpleEnum.Zero, SimpleEnum.One, SimpleEnum.Two)) set(ExtensionsTest.field19, 12u) set(ExtensionsTest.field20, 14uL) diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/DSLBuilderTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/DSLBuilderTest.kt index 83ed008f..14ad7641 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/DSLBuilderTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/DSLBuilderTest.kt @@ -117,7 +117,7 @@ class DSLBuilderTest { @Test fun testCreateEnumMessage() { - val enum = SimpleEnum.ONE + val enum = SimpleEnum.One val msg = messageWithEnum { field1 = enum @@ -128,7 +128,7 @@ class DSLBuilderTest { @Test fun testCreateRepeatedEnumMessage() { - val list = listOf(SimpleEnum.ONE, SimpleEnum.TWO, SimpleEnum.ZERO, SimpleEnum.ONE) + val list = listOf(SimpleEnum.One, SimpleEnum.Two, SimpleEnum.Zero, SimpleEnum.One) val msg = messageWithRepeatedEnum { field1List += list @@ -191,11 +191,11 @@ class DSLBuilderTest { @Test fun testCreateMessageWithEnumMap() { val map = mapOf( - 33 to SimpleEnum.ONE, - 12 to SimpleEnum.TWO, - 13 to SimpleEnum.TWO, - -12 to SimpleEnum.ONE, - 5 to SimpleEnum.ZERO + 33 to SimpleEnum.One, + 12 to SimpleEnum.Two, + 13 to SimpleEnum.Two, + -12 to SimpleEnum.One, + 5 to SimpleEnum.Zero ) val msg = messageWithEnumMap { diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumMapFieldSerializationTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumMapFieldSerializationTest.kt index 2f92eccc..32923a8b 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumMapFieldSerializationTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumMapFieldSerializationTest.kt @@ -15,8 +15,8 @@ class OpenClosedEnumMapFieldSerializationTest { 2 to 1 ), expectedValues = mapOf( - 1 to ClosedEnumTest.ClosedEnum.DEFAULT, - 2 to ClosedEnumTest.ClosedEnum.ONE, + 1 to ClosedEnumTest.ClosedEnum.Default, + 2 to ClosedEnumTest.ClosedEnum.One, ), expectedUnknownFieldCount = 0 ) @@ -28,7 +28,7 @@ class OpenClosedEnumMapFieldSerializationTest { 3 to 2 ), expectedValues = mapOf( - 2 to ClosedEnumTest.ClosedEnum.ONE + 2 to ClosedEnumTest.ClosedEnum.One ), expectedUnknownFieldCount = 2 ) @@ -57,8 +57,8 @@ class OpenClosedEnumMapFieldSerializationTest { 2 to 1 ), expectedValues = mapOf( - 1 to OpenEnumTest.OpenEnum.DEFAULT, - 2 to OpenEnumTest.OpenEnum.ONE, + 1 to OpenEnumTest.OpenEnum.Default, + 2 to OpenEnumTest.OpenEnum.One, ), expectedUnknownFieldCount = 0 ) @@ -71,7 +71,7 @@ class OpenClosedEnumMapFieldSerializationTest { ), expectedValues = mapOf( 1 to OpenEnumTest.OpenEnum.Unrecognized(-1), - 2 to OpenEnumTest.OpenEnum.ONE, + 2 to OpenEnumTest.OpenEnum.One, 3 to OpenEnumTest.OpenEnum.Unrecognized(2) ), expectedUnknownFieldCount = 0 diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumOneOfSerializationTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumOneOfSerializationTest.kt index 32b092bd..24eb17ee 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumOneOfSerializationTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumOneOfSerializationTest.kt @@ -13,14 +13,14 @@ class OpenClosedEnumOneOfSerializationTest { runClosedOneOfDeserializationTest( UnknownField.Varint(1, 0), ClosedEnumTest.MessageWithClosedOneOf.A.B( - ClosedEnumTest.ClosedEnum.DEFAULT + ClosedEnumTest.ClosedEnum.Default ), emptyList() ) runClosedOneOfDeserializationTest( UnknownField.Varint(1, 1), ClosedEnumTest.MessageWithClosedOneOf.A.B( - ClosedEnumTest.ClosedEnum.ONE + ClosedEnumTest.ClosedEnum.One ), emptyList() ) @@ -49,14 +49,14 @@ class OpenClosedEnumOneOfSerializationTest { runOpenOneOfDeserializationTest( UnknownField.Varint(1, 0), OpenEnumTest.MessageWithOpenOneOf.A.B( - OpenEnumTest.OpenEnum.DEFAULT + OpenEnumTest.OpenEnum.Default ), emptyList() ) runOpenOneOfDeserializationTest( UnknownField.Varint(1, 1), OpenEnumTest.MessageWithOpenOneOf.A.B( - OpenEnumTest.OpenEnum.ONE + OpenEnumTest.OpenEnum.One ), emptyList() ) diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumRepeatedFieldSerializationTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumRepeatedFieldSerializationTest.kt index cac267c7..e9ab4113 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumRepeatedFieldSerializationTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumRepeatedFieldSerializationTest.kt @@ -13,16 +13,16 @@ class OpenClosedEnumRepeatedFieldSerializationTest { fun testClosedRepeatedDeserializationScenarios() { runClosedRepeatedDeserializationTest( fields = listOf(0), - expectedValues = listOf(ClosedEnumTest.ClosedEnum.DEFAULT), + expectedValues = listOf(ClosedEnumTest.ClosedEnum.Default), expectedUnknownFields = emptyList() ) runClosedRepeatedDeserializationTest( fields = listOf(0, 1, 0), expectedValues = listOf( - ClosedEnumTest.ClosedEnum.DEFAULT, - ClosedEnumTest.ClosedEnum.ONE, - ClosedEnumTest.ClosedEnum.DEFAULT + ClosedEnumTest.ClosedEnum.Default, + ClosedEnumTest.ClosedEnum.One, + ClosedEnumTest.ClosedEnum.Default ), expectedUnknownFields = emptyList() ) @@ -30,7 +30,7 @@ class OpenClosedEnumRepeatedFieldSerializationTest { runClosedRepeatedDeserializationTest( fields = listOf(-4, 3, 1), expectedValues = listOf( - ClosedEnumTest.ClosedEnum.ONE + ClosedEnumTest.ClosedEnum.One ), expectedUnknownFields = listOf( UnknownField.Varint(1, -4), @@ -68,16 +68,16 @@ class OpenClosedEnumRepeatedFieldSerializationTest { fun testOpenRepeatedDeserializationScenarios() { runOpenRepeatedDeserializationTest( fields = listOf(0), - expectedValues = listOf(OpenEnumTest.OpenEnum.DEFAULT), + expectedValues = listOf(OpenEnumTest.OpenEnum.Default), expectedUnknownFields = emptyList() ) runOpenRepeatedDeserializationTest( fields = listOf(0, 1, 0), expectedValues = listOf( - OpenEnumTest.OpenEnum.DEFAULT, - OpenEnumTest.OpenEnum.ONE, - OpenEnumTest.OpenEnum.DEFAULT + OpenEnumTest.OpenEnum.Default, + OpenEnumTest.OpenEnum.One, + OpenEnumTest.OpenEnum.Default ), expectedUnknownFields = emptyList() ) @@ -87,7 +87,7 @@ class OpenClosedEnumRepeatedFieldSerializationTest { expectedValues = listOf( OpenEnumTest.OpenEnum.Unrecognized(-4), OpenEnumTest.OpenEnum.Unrecognized(3), - OpenEnumTest.OpenEnum.ONE, + OpenEnumTest.OpenEnum.One, ), expectedUnknownFields = emptyList() ) diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumScalarFieldSerializationTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumScalarFieldSerializationTest.kt index b1c97e38..31710823 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumScalarFieldSerializationTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/serialization/OpenClosedEnumScalarFieldSerializationTest.kt @@ -10,11 +10,11 @@ class OpenClosedEnumScalarFieldSerializationTest { @Test fun testClosedScalarDeserializationScenarios() { - runClosedScalarDeserializationTest(UnknownField.Varint(1, 0), ClosedEnumTest.ClosedEnum.DEFAULT, emptyList()) - runClosedScalarDeserializationTest(UnknownField.Varint(1, 1), ClosedEnumTest.ClosedEnum.ONE, emptyList()) + runClosedScalarDeserializationTest(UnknownField.Varint(1, 0), ClosedEnumTest.ClosedEnum.Default, emptyList()) + runClosedScalarDeserializationTest(UnknownField.Varint(1, 1), ClosedEnumTest.ClosedEnum.One, emptyList()) runClosedScalarDeserializationTest( UnknownField.Varint(1, 2), - ClosedEnumTest.ClosedEnum.DEFAULT, + ClosedEnumTest.ClosedEnum.Default, listOf(UnknownField.Varint(1, 2)) ) } @@ -34,8 +34,8 @@ class OpenClosedEnumScalarFieldSerializationTest { @Test fun testOpenScalarDeserializationScenarios() { - runOpenScalarDeserializationTest(UnknownField.Varint(1, 0), OpenEnumTest.OpenEnum.DEFAULT, emptyList()) - runOpenScalarDeserializationTest(UnknownField.Varint(1, 1), OpenEnumTest.OpenEnum.ONE, emptyList()) + runOpenScalarDeserializationTest(UnknownField.Varint(1, 0), OpenEnumTest.OpenEnum.Default, emptyList()) + runOpenScalarDeserializationTest(UnknownField.Varint(1, 1), OpenEnumTest.OpenEnum.One, emptyList()) runOpenScalarDeserializationTest( UnknownField.Varint(1, 2), OpenEnumTest.OpenEnum.Unrecognized(2), From 512693e45d860e2679d5e0019604280a7f3143d4 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:03:14 +0100 Subject: [PATCH 07/14] Fix tests --- .../src/jvmMacOsTest/kotlin/DefaultCertificatesRpcTest.kt | 4 ++-- .../modeltree/DefaultEnumValueTest.kt | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/kmp-grpc-internal-test/src/jvmMacOsTest/kotlin/DefaultCertificatesRpcTest.kt b/kmp-grpc-internal-test/src/jvmMacOsTest/kotlin/DefaultCertificatesRpcTest.kt index 9ecc13e1..df0f9905 100644 --- a/kmp-grpc-internal-test/src/jvmMacOsTest/kotlin/DefaultCertificatesRpcTest.kt +++ b/kmp-grpc-internal-test/src/jvmMacOsTest/kotlin/DefaultCertificatesRpcTest.kt @@ -24,7 +24,7 @@ abstract class DefaultCertificatesRpcTest : ServerTest { val stub = HelloServiceStub(channelWithCertificate) val msg = HelloRequest(greeting = "Hello World") - stub.SayHello(msg) + stub.sayHello(msg) } @Test @@ -32,6 +32,6 @@ abstract class DefaultCertificatesRpcTest : ServerTest { val stub = HelloServiceStub(channelWithoutCertificates) val msg = HelloRequest(greeting = "Hello World") - assertFailsWith { stub.SayHello(msg) } + assertFailsWith { stub.sayHello(msg) } } } diff --git a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DefaultEnumValueTest.kt b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DefaultEnumValueTest.kt index f7811432..2cf242b5 100644 --- a/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DefaultEnumValueTest.kt +++ b/kmp-grpc-plugin/src/test/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/modeltree/DefaultEnumValueTest.kt @@ -2,6 +2,7 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.modeltree import com.google.testing.junit.testparameterinjector.junit5.TestParameter import com.google.testing.junit.testparameterinjector.junit5.TestParameterInjectorTest +import io.github.timortel.kmpgrpc.plugin.NamingStrategy import io.github.timortel.kmpgrpc.plugin.sourcegeneration.model.declaration.message.field.ProtoMessageField import io.github.timortel.kotlin_multiplatform_grpc_plugin.validation.BaseValidationTest import org.junit.jupiter.api.Assertions @@ -66,7 +67,11 @@ class DefaultEnumValueTest : BaseModelTreeTest() { version: BaseValidationTest.ProtoVersion, expectedDefaultValue: String ) { - val project = buildProject(proto.trimIndent(), version) + val project = buildProject( + content = proto.trimIndent(), + protoVersion = version, + namingStrategy = NamingStrategy.KOTLIN_IDIOMATIC + ) val field = project .findMessage("C") From cd830257f12583a36edbb969fe057acebdda50d0 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:56:04 +0100 Subject: [PATCH 08/14] Also consider extensions for isInitialized. --- .../proto/proto2/proto2-required-fields.proto | 12 ++++ .../test/model/IsInitializedTest.kt | 69 ++++++++++++++++++- .../extensions/IsInitializedFieldExtension.kt | 53 +++++++++++++- readme.md | 2 +- 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto b/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto index 02099df0..ec78ef18 100644 --- a/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto +++ b/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto @@ -20,3 +20,15 @@ message Proto2MessageWithRequiredFields { string field6 = 6; } } + +message Proto2MessageWithRequiredExtension { + extensions 1 to max; +} + +extend Proto2MessageWithRequiredExtension { + required string extension1 = 1; + + required Proto2MessageWithMixedFields extensionRequiredMsg = 2; + + repeated Proto2MessageWithMixedFields extensionRepeatedMsg = 3; +} diff --git a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/IsInitializedTest.kt b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/IsInitializedTest.kt index 7975686c..f220a28a 100644 --- a/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/IsInitializedTest.kt +++ b/kmp-grpc-internal-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/model/IsInitializedTest.kt @@ -1,6 +1,9 @@ package io.github.timortel.kotlin_multiplatform_grpc_plugin.test.model +import io.github.timortel.kmpgrpc.core.message.extensions.buildExtensions import io.github.timortel.kmpgrpc.test.proto2.Proto2RequiredFields +import io.github.timortel.kmpgrpc.test.proto2.Proto2RequiredFields.Proto2MessageWithMixedFields +import io.github.timortel.kmpgrpc.test.proto2.Proto2RequiredFields.Proto2MessageWithRequiredExtension import io.github.timortel.kmpgrpc.test.proto2.Proto2RequiredFields.Proto2MessageWithRequiredFields import kotlin.test.Test import kotlin.test.assertFalse @@ -25,7 +28,7 @@ class IsInitializedTest { @Test fun testUninitializedNestedMessage() { // field2 is set, but the nested message itself is missing its own required field1 - val incompleteNested = Proto2RequiredFields.Proto2MessageWithMixedFields.createPartial(field1 = null) + val incompleteNested = Proto2MessageWithMixedFields.createPartial(field1 = null) val msg = Proto2MessageWithRequiredFields.createPartial( field1 = "valid", field2 = incompleteNested @@ -41,7 +44,10 @@ class IsInitializedTest { field3List = listOf(incomplete) ) - assertFalse(msg.isInitialized, "Message should be uninitialized if any element in a repeated field is uninitialized") + assertFalse( + msg.isInitialized, + "Message should be uninitialized if any element in a repeated field is uninitialized" + ) } @Test @@ -57,7 +63,7 @@ class IsInitializedTest { @Test fun testOneOfInitialization() { // x.field5 is a message type. If that message is incomplete, the parent is incomplete. - val incompleteMixed = Proto2RequiredFields.Proto2MessageWithMixedFields.createPartial(field1 = null) + val incompleteMixed = Proto2MessageWithMixedFields.createPartial(field1 = null) val msg = Proto2MessageWithRequiredFields( x = Proto2MessageWithRequiredFields.X.Field5(incompleteMixed) ) @@ -70,4 +76,61 @@ class IsInitializedTest { ) assertTrue(msg2.isInitialized, "Message should be initialized if OneOf contains a valid string") } + + @Test + fun testRequiredMessageExtensionInitialization() { + // 1. Missing both required extensions + val emptyMsg = Proto2MessageWithRequiredExtension.createPartial() + assertFalse(emptyMsg.isInitialized, "Should be uninitialized: missing extension1 and extensionRequiredMsg") + + // 2. extension1 is present, but extensionRequiredMsg is missing + val partialExt1 = buildExtensions { + set(Proto2RequiredFields.extension1, "valid") + } + val msgOnlyExt1 = Proto2MessageWithRequiredExtension.createPartial(extensions = partialExt1) + assertFalse(msgOnlyExt1.isInitialized, "Should be uninitialized: missing required message extension") + + // 3. Both present, but the required message extension is itself uninitialized + val incompleteNested = Proto2MessageWithMixedFields.createPartial(field1 = null) + val partialExt2 = buildExtensions { + set(Proto2RequiredFields.extension1, "valid") + set(Proto2RequiredFields.extensionRequiredMsg, incompleteNested) + } + val msgIncompleteMsg = Proto2MessageWithRequiredExtension.createPartial(extensions = partialExt2) + assertFalse( + msgIncompleteMsg.isInitialized, + "Should be uninitialized: required message extension is missing field1" + ) + + // 4. Fully initialized + val completeExt = buildExtensions { + set(Proto2RequiredFields.extension1, "valid") + set(Proto2RequiredFields.extensionRequiredMsg, Proto2MessageWithMixedFields(field1 = "valid")) + } + val validMsg = Proto2MessageWithRequiredExtension(extensions = completeExt) + assertTrue( + validMsg.isInitialized, + "Should be initialized: all required extensions and their fields are present" + ) + } + + @Test + fun testRepeatedMessageExtensionInitialization() { + val validNested = Proto2MessageWithMixedFields(field1 = "ok") + val incompleteNested = Proto2MessageWithMixedFields.createPartial(field1 = null) + + // Base valid extensions so the parent's 'required' constraints are met + val baseExtensions = buildExtensions { + set(Proto2RequiredFields.extension1, "valid") + set(Proto2RequiredFields.extensionRequiredMsg, validNested) + set(Proto2RequiredFields.extensionRepeatedMsgList, listOf(validNested, incompleteNested)) + } + + val msg = Proto2MessageWithRequiredExtension(extensions = baseExtensions) + + assertFalse( + msg.isInitialized, + "Should be uninitialized: one element in the repeated message extension is uninitialized" + ) + } } diff --git a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt index 251cfe28..d213bf1b 100644 --- a/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt +++ b/kmp-grpc-plugin/src/main/java/io/github/timortel/kmpgrpc/plugin/sourcegeneration/generators/protofile/message/extensions/IsInitializedFieldExtension.kt @@ -2,6 +2,7 @@ package io.github.timortel.kmpgrpc.plugin.sourcegeneration.generators.protofile. import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.TypeSpec import io.github.timortel.kmpgrpc.plugin.sourcegeneration.SourceTarget import io.github.timortel.kmpgrpc.plugin.sourcegeneration.constants.Const @@ -34,7 +35,13 @@ object IsInitializedFieldExtension : MessageWriterExtension { val subMessages = subMessageFields + subMessageMapFields + oneOfs - if (requiredFields.isEmpty() && subMessages.isEmpty()) { + val consideredExtensionFields = message.extensionsInProject.flatMap { extensions -> + extensions.fields.filter { field -> + field.cardinality.isLegacyRequired || field.type.isMessage + } + } + + if (requiredFields.isEmpty() && subMessages.isEmpty() && consideredExtensionFields.isEmpty()) { add("true") } else { val separator = " && " @@ -51,6 +58,7 @@ object IsInitializedFieldExtension : MessageWriterExtension { Const.Message.isInitializedProperty.name ) } + ProtoFieldCardinality.Repeated -> { add( "%N.all { it.%N }", @@ -77,7 +85,48 @@ object IsInitializedFieldExtension : MessageWriterExtension { ) } - val impl = listOf(requiredFieldsBool, subMessageFieldsBool, subMessageOneOfFieldsBool, subMessageMapFieldsBool).joinCodeBlocks(separator) + val requiredExtensionFieldsBool = + consideredExtensionFields.joinToCodeBlock(separator) { field -> + val extensionMember = MemberName(field.file.className, field.codeName) + + when (field.cardinality) { + is ProtoFieldCardinality.Singular -> { + if (field.type.isMessage) { + add( + "%N[%M]?.%N == true", + Const.Message.Constructor.MessageExtensions.name, + extensionMember, + Const.Message.isInitializedProperty.name + ) + } else { + add( + "%N[%M] != null", + Const.Message.Constructor.MessageExtensions.name, + extensionMember + ) + } + } + + ProtoFieldCardinality.Repeated -> { + if (field.type.isMessage) { + add( + "%N[%M].all { it.%N }", + Const.Message.Constructor.MessageExtensions.name, + extensionMember, + Const.Message.isInitializedProperty.name + ) + } + } + } + } + + val impl = listOf( + requiredFieldsBool, + subMessageFieldsBool, + subMessageOneOfFieldsBool, + subMessageMapFieldsBool, + requiredExtensionFieldsBool + ).joinCodeBlocks(separator) add(impl) } diff --git a/readme.md b/readme.md index 94e5e0ae..fef6a1ee 100644 --- a/readme.md +++ b/readme.md @@ -367,7 +367,7 @@ You can construct a message of type `MyMessage` like this: val msg = Sample.MyMessage( regularField = "val1", extensions = buildExtensions { - set[Sample.myExtension] = "val2" + set(Sample.myExtension, "val2") } ) ``` From ae6f21406e926f5d5590ee998fb83ad447e5c12e Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:18:24 +0100 Subject: [PATCH 09/14] Bump versions --- gradle/libs.versions.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04af1f91..a2c1380c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,20 +7,20 @@ androidGradlePlugin = "8.11.2" androidCompileSdk = "36" androidMinSdk = "21" -kotlin = "2.2.21" -kotlinxIo = "0.8.0" +kotlin = "2.3.10" +kotlinxIo = "0.9.0" kotlinxCoroutines = "1.10.2" -ktor = "3.3.2" -okio = "3.16.3" +ktor = "3.4.1" +okio = "3.16.4" -grpcJvm = "1.76.0" +grpcJvm = "1.79.0" grpcKotlin = "1.5.0" # Plugin Relevant antlr = "4.13.2" -gradlePluginPublish = "2.0.0" -buildConfigPlugin = "5.7.1" +gradlePluginPublish = "2.1.0" +buildConfigPlugin = "6.0.9" kotlinPoet = "2.2.0" From 4851a455d4386a2032c21ab3bf09110da96ae7f7 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:19:12 +0100 Subject: [PATCH 10/14] Bump versions --- examples/compose-example/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/compose-example/gradle/libs.versions.toml b/examples/compose-example/gradle/libs.versions.toml index 86ac19d6..f97b1b94 100644 --- a/examples/compose-example/gradle/libs.versions.toml +++ b/examples/compose-example/gradle/libs.versions.toml @@ -11,7 +11,7 @@ compose-multiplatform = "1.8.2" grpcKotlinStub = "1.5.0" grpc = "1.76.0" junit = "4.13.2" -kotlin = "2.2.21" +kotlin = "2.3.10" kotlinx-coroutines = "1.10.2" kmpGrpc = "2.0.0" protobuf = "3.25.6" From ac05156374283bc68c49f7d1fb144fe298d96d41 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:32:20 +0100 Subject: [PATCH 11/14] Fix tests. --- .../src/commonMain/proto/proto2/proto2-required-fields.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto b/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto index ec78ef18..abe72bbe 100644 --- a/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto +++ b/kmp-grpc-internal-test/src/commonMain/proto/proto2/proto2-required-fields.proto @@ -26,9 +26,9 @@ message Proto2MessageWithRequiredExtension { } extend Proto2MessageWithRequiredExtension { - required string extension1 = 1; + optional string extension1 = 1; - required Proto2MessageWithMixedFields extensionRequiredMsg = 2; + optional Proto2MessageWithMixedFields extensionRequiredMsg = 2; repeated Proto2MessageWithMixedFields extensionRepeatedMsg = 3; } From 32b0ec940a016fe0731538a177374f37a78dc414 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:38:57 +0100 Subject: [PATCH 12/14] Update crates --- kmp-grpc-native/Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kmp-grpc-native/Cargo.toml b/kmp-grpc-native/Cargo.toml index cb452d29..55f5f2c9 100644 --- a/kmp-grpc-native/Cargo.toml +++ b/kmp-grpc-native/Cargo.toml @@ -6,13 +6,13 @@ edition = "2024" license = "Apache-2.0" [dependencies] -tonic = { version = "0.14.2", features = ["channel", "tls-ring", "tls-webpki-roots"] } -tokio = { version = "1.47.1", features = ["rt-multi-thread"] } -rustls-webpki = "0.103.8" -bytes = "1.11.0" +tonic = { version = "0.14.5", features = ["channel", "tls-ring", "tls-webpki-roots"] } +tokio = { version = "1.50.0", features = ["rt-multi-thread"] } +rustls-webpki = "0.103.9" +bytes = "1.11.1" once_cell = "1.21.3" -log = "0.4.28" -env_logger = "0.11.8" +log = "0.4.29" +env_logger = "0.11.9" [lib] crate-type = ["staticlib"] From 9d3233c24a8488f3827febccfe61afc8b2767f71 Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:20:33 +0100 Subject: [PATCH 13/14] Use channelflow on js targets for rpc implementation --- .../timortel/kmpgrpc/core/rpc/rpcimplementation.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kmp-grpc-core/src/jsTargetCommon/kotlin/io/github/timortel/kmpgrpc/core/rpc/rpcimplementation.kt b/kmp-grpc-core/src/jsTargetCommon/kotlin/io/github/timortel/kmpgrpc/core/rpc/rpcimplementation.kt index f4c7670d..5a1221c5 100644 --- a/kmp-grpc-core/src/jsTargetCommon/kotlin/io/github/timortel/kmpgrpc/core/rpc/rpcimplementation.kt +++ b/kmp-grpc-core/src/jsTargetCommon/kotlin/io/github/timortel/kmpgrpc/core/rpc/rpcimplementation.kt @@ -15,9 +15,9 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.utils.io.* import io.ktor.utils.io.core.* +import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.channelFlow import kotlinx.io.Buffer import kotlinx.io.Source import kotlinx.io.readByteArray @@ -92,7 +92,7 @@ private fun grpcImplementation( ): Flow { val metadata = callOptions.metadata - return flow { + return channelFlow { channel.registerRpc() try { @@ -166,6 +166,7 @@ private fun grpcImplementation( throw StatusException.internal("Unexpected timeout except caught.", e) } } catch (t: Throwable) { + t.printStackTrace() throw StatusException( status = Status( code = Code.UNAVAILABLE, @@ -179,7 +180,7 @@ private fun grpcImplementation( } } -private suspend fun FlowCollector.readResponse( +private suspend fun ProducerScope.readResponse( channel: ByteReadChannel, methodDescriptor: MethodDescriptor, deserializer: MessageDeserializer, @@ -204,7 +205,7 @@ private suspend fun FlowCollector.readResponse( interceptor.onReceiveMessage(methodDescriptor, currentMessage) } - emit(actualMessage) + send(actualMessage) } else if (flag == 0x80.toUByte()) { val headers = decodeHeadersFrame(payload) From 78272190ba301aec1218c1b90348cc9f9930a7fc Mon Sep 17 00:00:00 2001 From: Tim Ortel <100865202+TimOrtel@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:31:09 +0100 Subject: [PATCH 14/14] Fix release script. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 829dd7c0..22de322a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: --verbose \ --header "Authorization: Bearer $TOKEN" \ --form bundle=@kmp-grpc-core-${{ inputs.version }}.zip \ - "https://central.sonatype.com/api/v1/publisher/upload?publishingType=USER_MANAGED&name=io.github.timortel%3Akmp-grpc-core%3A${{ inputs.version }} + "https://central.sonatype.com/api/v1/publisher/upload?publishingType=USER_MANAGED&name=io.github.timortel%3Akmp-grpc-core%3A${{ inputs.version }}") echo "deployment_id=$RESPONSE" >> "$GITHUB_OUTPUT" - name: Wait for deployment to be ready