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

EnumMode enum option #2993

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,56 @@ class WirePluginTest {
.contains("""@CustomerSupportUrlOption("https://en.wikipedia.org/wiki/Customer_support")""")
}

@Test
fun kotlinEnumMode() {
val fixtureRoot = File("src/test/projects/kotlin-enum-mode")

val result = gradleRunner.runFixture(fixtureRoot) { build() }

assertThat(result.task(":generateProtos")).isNotNull()
assertThat(result.output).contains("Writing com.squareup.enum.geology.Period")
assertThat(result.output).contains("Writing com.squareup.enum.geology.Continent")
assertThat(result.output).contains("Writing com.squareup.enum.geology.Drink")
assertThat(result.output).contains("Writing com.squareup.sealed.geology.Period")
assertThat(result.output).contains("Writing com.squareup.sealed.geology.Continent")
assertThat(result.output).contains("Writing com.squareup.sealed.geology.Drink")

// Wire has been configured so that `Continent` should always be the opposite of the global
// setting while `Period` and `Drink` match it.

val enumPeriod = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/enum/geology/Period.kt",
).readText()
assertThat(enumPeriod).contains("enum class Period")
val enumContinent = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/enum/geology/Continent.kt",
).readText()
assertThat(enumContinent).contains("sealed class Continent")
val enumDrink = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/enum/geology/Drink.kt",
).readText()
assertThat(enumDrink).contains("enum class Drink")

val sealedPeriod = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/sealed/geology/Period.kt",
).readText()
assertThat(sealedPeriod).contains("sealed class Period")
val sealedContinent = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/sealed/geology/Continent.kt",
).readText()
assertThat(sealedContinent).contains("enum class Continent")
val sealedDrink = File(
fixtureRoot,
"build/generated/source/wire/com/squareup/sealed/geology/Drink.kt",
).readText()
assertThat(sealedDrink).contains("sealed class Drink")
}

@Test
fun packageCycles() {
val fixtureRoot = File("src/test/projects/package-cycles")
Expand Down
16 changes: 16 additions & 0 deletions wire-gradle-plugin/src/test/projects/kotlin-enum-mode/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm'
id 'com.squareup.wire'
}

wire {
kotlin {
includes = ["enum.geology.*"]
enumMode = "enum_class"
}
kotlin {
includes = ["sealed.geology.*"]
enumMode = "sealed_class"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
syntax = "proto2";

package enum.geology;

import "wire/extensions.proto";

option java_package = "com.squareup.enum.geology";

// Target is set to "enum_class" so the option should not have any effect.
enum Period {
option (wire.enum_mode) = "enum_class";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Love this

CRETACEOUS = 1;
JURASSIC = 2;
TRIASSIC = 3;
}

// Target is set to "enum_class" so the option should takes precedence.
enum Continent {
option (wire.enum_mode) = "sealed_class";
AFRICA = 0;
AMERICA = 1;
ANTARCTICA = 2;
ASIA = 3;
AUSTRALIA = 4;
EUROPE = 5;
}

enum Drink {
UNKNOWN = 0;
PEPSI = 1;
MOUNTAIN_DEW = 2;
ROOT_BEER = 9;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
syntax = "proto2";

package sealed.geology;

import "wire/extensions.proto";

option java_package = "com.squareup.sealed.geology";

// Target is set to "sealed_class" so the option should not have any effect.
enum Period {
option (wire.enum_mode) = "sealed_class";
CRETACEOUS = 1;
JURASSIC = 2;
TRIASSIC = 3;
}

// Target is set to "sealed_class" so the option should takes precedence.
enum Continent {
option (wire.enum_mode) = "enum_class";
AFRICA = 0;
AMERICA = 1;
ANTARCTICA = 2;
ASIA = 3;
AUSTRALIA = 4;
EUROPE = 5;
}

enum Drink {
UNKNOWN = 0;
PEPSI = 1;
MOUNTAIN_DEW = 2;
ROOT_BEER = 9;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2257,7 +2257,13 @@ class KotlinGenerator private constructor(
.addParameter(valueName, Int::class)
val builder: TypeSpec.Builder

when (enumMode) {
val enumModeOption = when (enum.enumMode?.lowercase()) {
"enum_class" -> ENUM_CLASS
"sealed_class" -> SEALED_CLASS
else -> null
}

when (enumModeOption ?: enumMode) {
ENUM_CLASS -> {
builder = TypeSpec.enumBuilder(type.simpleName)
.apply {
Expand Down
1 change: 1 addition & 0 deletions wire-schema/api/wire-schema.api
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public final class com/squareup/wire/schema/EnumType : com/squareup/wire/schema/
public static final fun fromElement (Lcom/squareup/wire/schema/ProtoType;Lcom/squareup/wire/schema/internal/parser/EnumElement;Lcom/squareup/wire/Syntax;)Lcom/squareup/wire/schema/EnumType;
public final fun getConstants ()Ljava/util/List;
public fun getDocumentation ()Ljava/lang/String;
public final fun getEnumMode ()Ljava/lang/String;
public fun getLocation ()Lcom/squareup/wire/schema/Location;
public fun getName ()Ljava/lang/String;
public fun getNestedExtendList ()Ljava/util/List;
Expand Down
2 changes: 2 additions & 0 deletions wire-schema/api/wire-schema.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,8 @@ final class com.squareup.wire.schema/EnumType : com.squareup.wire.schema/Type {
final fun <get-constants>(): kotlin.collections/List<com.squareup.wire.schema/EnumConstant> // com.squareup.wire.schema/EnumType.constants.<get-constants>|<get-constants>(){}[0]
final val documentation // com.squareup.wire.schema/EnumType.documentation|{}documentation[0]
final fun <get-documentation>(): kotlin/String // com.squareup.wire.schema/EnumType.documentation.<get-documentation>|<get-documentation>(){}[0]
final val enumMode // com.squareup.wire.schema/EnumType.enumMode|{}enumMode[0]
final fun <get-enumMode>(): kotlin/String? // com.squareup.wire.schema/EnumType.enumMode.<get-enumMode>|<get-enumMode>(){}[0]
final val isDeprecated // com.squareup.wire.schema/EnumType.isDeprecated|{}isDeprecated[0]
final fun <get-isDeprecated>(): kotlin/Boolean // com.squareup.wire.schema/EnumType.isDeprecated.<get-isDeprecated>|<get-isDeprecated>(){}[0]
final val location // com.squareup.wire.schema/EnumType.location|{}location[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ data class EnumType(
val isDeprecated: Boolean
get() = "true" == deprecated

val enumMode: String?
get() = options.get(WIRE_ENUM_MODE)?.toString()

/** Returns the constant named `name`, or null if this enum has no such constant. */
fun constant(name: String) = constants.find { it.name == name }

Expand Down Expand Up @@ -197,6 +200,7 @@ data class EnumType(
companion object {
internal val ALLOW_ALIAS = ProtoMember.get(ENUM_OPTIONS, "allow_alias")
internal val DEPRECATED = ProtoMember.get(ENUM_OPTIONS, "deprecated")
internal val WIRE_ENUM_MODE = ProtoMember.get(ENUM_OPTIONS, "wire.enum_mode")

@JvmStatic
fun fromElement(
Expand Down
12 changes: 12 additions & 0 deletions wire-schema/src/jvmMain/resources/wire/extensions.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ extend google.protobuf.FieldOptions {
optional bool use_array = 1185;
}

extend google.protobuf.EnumOptions {
/**
* Defines how an enum type is to be generated. This is only supported by Kotlin generation.
* - 'enum_class': the enum type will be generated as a Kotlin enum class.
* - 'sealed_class': the enum type will be generated as a Kotlin sealed class.
*
* When set, the value of this option takes precedence over the global enum mode that may be set
* at the Kotlin target level in the Wire Gradle plugin.
*/
optional string enum_mode = 1190;
Comment on lines +47 to +55
Copy link
Member Author

Choose a reason for hiding this comment

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

even though enum_class and sealed_class are Kotlin words, I think we'd wanna use this option on Java generation as well later, even if they are less explicit.
Hence the option name to enum_mode instead of something like kotlin_enum

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like it

}

extend google.protobuf.EnumValueOptions {
/**
* Annotates an enum constant that starts to be available with a specified version.
Expand Down