Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Gen Class Without Any Properties #15

Merged
merged 4 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 66 additions & 4 deletions app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.nio.file.Path
import kotlin.io.path.Path


/**
* Links are used to keep track of the [TypeName] of the fields and the types they reference before the code
* is generated. This way, code generation of types do not depend on each other and can be generated in any
Expand Down Expand Up @@ -144,8 +145,13 @@

fileDescriptor.messageTypes.forEach { messageType ->
if (shouldGenerateClass(messageType)) {
val typeSpec = generateSingleClass(messageType)
fileSpec.addType(typeSpec.build())
if (isHasPropertyField(messageType)) {
val typeSpec = generateDataClass(messageType)
fileSpec.addType(typeSpec.build())

Check warning on line 150 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L149-L150

Added lines #L149 - L150 were not covered by tests
} else {
val typeSpec = generateClass(messageType)
fileSpec.addType(typeSpec.build())

Check warning on line 153 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L152-L153

Added lines #L152 - L153 were not covered by tests
}
}
}

Expand All @@ -164,6 +170,19 @@
return fileSpec
}

/**
* Generate the code for the given [Descriptors.Descriptor]. Returns a Boolean determine whether the
* descriptor has a property field or not.
*
* @param fileDescriptor [Descriptors.FileDescriptor] to generate code for.
* @return Boolean that represents whether the descriptor has a property field or not.
*/
private fun isHasPropertyField(fileDescriptor: Descriptors.Descriptor): Boolean {
tamnguyenhuy marked this conversation as resolved.
Show resolved Hide resolved
return fileDescriptor.fields.any {

Check warning on line 181 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L181

Added line #L181 was not covered by tests
it.isOptional || it.isRequired || it.isRepeated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a question, in proto3 if optional int32 x = 1 is considered optional and in proto2 if required int32 x = 1 is considered required, is this going to return false int32 x = 1?

I think we should have a couple of unit tests to verify this doesn't break anything.

}
}

/**
* Generate a single parameter for the given [Descriptors.FieldDescriptor]. Returns a
* [ParameterSpec.Builder] so users can add additional code to the parameter.
Expand Down Expand Up @@ -193,7 +212,7 @@
* @param messageDescriptor [Descriptors.Descriptor] to generate code for.
* @return [TypeSpec.Builder] that contains the generated code.
*/
private fun generateSingleClass(messageDescriptor: Descriptors.Descriptor): TypeSpec.Builder {
private fun generateDataClass(messageDescriptor: Descriptors.Descriptor): TypeSpec.Builder {
val typeSpec = TypeSpec.classBuilder(messageDescriptor.name)
.addModifiers(KModifier.DATA)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I would suggest to simplify the code and reduce duplication is to add this modifier conditionally based on isHasPropertyField call. Simply we can hide this logic behind the generateSingleClass method so users don't have to worry about figuring out which method to call 🙂

.addAnnotation(Serializable::class)
Expand Down Expand Up @@ -233,6 +252,7 @@

typeSpec.addProperty(
PropertySpec.builder(fieldName, type)
.mutable(true) // change val to var

Check warning on line 255 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L255

Added line #L255 was not covered by tests
tamnguyenhuy marked this conversation as resolved.
Show resolved Hide resolved
.initializer(fieldName)
.build()
)
Expand All @@ -244,7 +264,11 @@
}
.filter { shouldGenerateClass(it) }
.map {
generateSingleClass(it).build()
if (isHasPropertyField(it)) {
generateDataClass(it).build()

Check warning on line 268 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L268

Added line #L268 was not covered by tests
} else {
generateClass(it).build()

Check warning on line 270 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L270

Added line #L270 was not covered by tests
}
tamnguyenhuy marked this conversation as resolved.
Show resolved Hide resolved
}
typeSpec.addTypes(nestedTypes)

Expand Down Expand Up @@ -282,6 +306,44 @@
return typeSpec
}

private fun generateClass(messageDescriptor: Descriptors.Descriptor): TypeSpec.Builder {
val typeSpec = TypeSpec.classBuilder(messageDescriptor.name)
.addAnnotation(Serializable::class)

Check warning on line 311 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L310-L311

Added lines #L310 - L311 were not covered by tests

// Generate parameters and properties
messageDescriptor.fields.forEach { fieldDescriptor ->
val type = TypeNames.typeNameOf(fieldDescriptor, typeLinks)
val fieldName = fieldDescriptor.name.toLowerCamelCaseIf(options.useCamelCase)
typeSpec.addProperty(
PropertySpec.builder(fieldName, type)
.mutable(true)
.initializer(fieldName)
.build()

Check warning on line 321 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L314-L321

Added lines #L314 - L321 were not covered by tests
)
}

Check warning on line 323 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L323

Added line #L323 was not covered by tests

// Recursively generate nested classes and enums
val nestedTypes = messageDescriptor.nestedTypes.filterNot {
it.options.mapEntry

Check warning on line 327 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L326-L327

Added lines #L326 - L327 were not covered by tests
}
.filter { shouldGenerateClass(it) }
.map {

Check warning on line 330 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L329-L330

Added lines #L329 - L330 were not covered by tests
if (isHasPropertyField(it)) {
generateDataClass(it).build()

Check warning on line 332 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L332

Added line #L332 was not covered by tests
} else {
generateClass(it).build()

Check warning on line 334 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L334

Added line #L334 was not covered by tests
}
}
typeSpec.addTypes(nestedTypes)

Check warning on line 337 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L337

Added line #L337 was not covered by tests

val nestedEnums = messageDescriptor.enumTypes.map {
generateSingleEnum(it).build()

Check warning on line 340 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L339-L340

Added lines #L339 - L340 were not covered by tests
}
typeSpec.addTypes(nestedEnums)

Check warning on line 342 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L342

Added line #L342 was not covered by tests

return typeSpec

Check warning on line 344 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L344

Added line #L344 was not covered by tests
}

/**
* Generate a single service for the given [Descriptors.ServiceDescriptor]. Returns a [TypeSpec.Builder] so
* users can add additional code to the service.
Expand Down
14 changes: 14 additions & 0 deletions testProtos/data_class.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run ./gradlew build in your local? I commit all the generated classes under generated-code-tests. Also we should have a test there to verify an empty message without any properties are serialized correctly.


message Hello {
enum Type {
UNKNOWN = 0;
KNOWN = 1;
}
message SubHello {
message HelloExtend {
optional Type type = 1;
}
optional SubHello subHello = 1;
}
}